使用Redis命令incr并到期时的竞争条件

时间:2013-12-22 01:23:04

标签: redis increment race-condition

基于redis文档:http://redis.io/commands/incr

在段落模式:速率限制器2中 较短的版本代码:

value = INCR(ip)

IF value == 1 THEN
  EXPIRE(ip, 1)

据称有一种竞争条件让EXPIRE永远不会执行。这意味着ip的值可以某种方式从0反弹到2。

然而在我的想法中,由于Redis是单线程而INCR是一个原始命令,它本身不应该是原子的?即使两个客户几乎同时进行INCR,它们如何检索0或两者都检索2?

3 个答案:

答案 0 :(得分:13)

想象一下,在INCR命令已执行但执行EXPIRE之前,您断开了与redis服务器的连接。在这种情况下,您永远不会执行EXPIRE因为下一次代码调用会给出您的价值> 1.在redis文档中使用了术语竞争条件。但这不是一个成功的术语。更正确的术语是不完美的算法。所以这个案例不是关于两个或更多客户之间的竞争条件,而是关于现实世界中的特殊情况。例如,服务器连接丢失。

答案 1 :(得分:8)

仍然可以以原子方式实现您的目标:您可以使用EVAL命令。

EVAL用于执行在Redis服务器内部用Lua编写的脚本,其中最好的部分是执行此脚本,就像执行单个原子操作一样。

以下脚本可用于此目的:

  

local v = redis.call('INCR',ARGV [1])如果v == 1则redis.call('EXPIRE',ARGV [1],ARGV [2])结束返回v

逻辑非常简单:我们将INCR命令的返回值存储到标记为v的变量中,然后检查v值是否为1(第一个增量),如果是,我们调用命令{ {1}}为该键,然后我们返回v的值.ARGV [...]是传递给脚本的参数,ARGV [1]是键名,ARGV [2]是以秒为单位的超时时间。给定的密​​钥。

使用此脚本的示例:

  

&GT; eval“local v = redis.call('INCR',ARGV [1])如果v == 1则redis.call('EXPIRE',ARGV [1],ARGV [2])结束返回v”0 my_key 10 < / p>      

(整数)1

     

&GT; eval“local v = redis.call('INCR',ARGV [1])如果v == 1则redis.call('EXPIRE',ARGV [1],ARGV [2])结束返回v”0 my_key 10 < / p>      

(整数)2

     

&GT;得到my_key

     

“2”

[等待10秒]

  

&GT;得到my_key

     

(无)

答案 2 :(得分:1)

我遇到了同样的问题,那是怎么回事: value = INCR(ip) IF [value == 1 || PTTL(ip)== -1]然后   EXPIRE(ip,1)

如果2个客户端几乎同时执行PTTL,则获取-1,并设置几乎同时到期