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

Dav*_*Hsu 8 increment race-condition redis

基于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?

mis*_*ion 14

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

  • 我也在做incr然后过期...有什么办法可以防止这种情况?像原子增量和到期... lua脚本是唯一的选择吗? (2认同)

小智 9

仍然可以以原子方式实现您想要的:您可以使用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(第一个增量),如果是,我们调用该EXPIRE键的命令然后返回值v.ARGV [...]是传递给脚本的参数,ARGV [1]是密钥名称,ARGV [2]是给定密钥的超时秒数.

使用此脚本的示例:

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

(整数)1

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

(整数)2

>得到my_key

"2"

[等待10秒]

>得到my_key

(零)