【问题标题】:A race condition when using Redis command incr and expire使用 Redis 命令 incr 和 expire 时的竞争条件
【发布时间】:2014-01-10 14:14:04
【问题描述】:

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

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

值 = INCR(ip) 如果值 == 1 那么 过期(IP,1)

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

但是在我看来,由于 Redis 是单线程,而 INCR 是原始命令,它本身不应该是原子的吗?即使 2 个客户端几乎同时执行 INCR,他们怎么可能都检索 0 或都检索 2?

【问题讨论】:

    标签: redis increment race-condition


    【解决方案1】:

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

    【讨论】:

    • 我也在做 incr 然后过期...有什么办法可以防止这种情况发生吗?像 atomic incr 和 expire... 是 lua 脚本唯一的选择吗?
    【解决方案2】:

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

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

    以下脚本可用于此目的:

    local v = redis.call('INCR', ARGV[1]) if v == 1 then redis.call('EXPIRE', ARGV[1], ARGV[2]) end return v

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

    使用此脚本的示例:

    > eval "local v = redis.call('INCR', ARGV[1]) if v == 1 then redis.call('EXPIRE', ARGV[1], ARGV[2]) end return v" 0 my_key 10

    (整数)1

    > eval "local v = redis.call('INCR', ARGV[1]) if v == 1 then redis.call('EXPIRE', ARGV[1], ARGV[2]) end return v" 0 my_key 10

    (整数)2

    > 获取我的密钥

    “2”

    [等待 10 秒]

    > 获取我的密钥

    (无)

    【讨论】:

    • 我知道这个解决方案,只是对我发布的原始问题感到好奇。无论如何谢谢:)
    【解决方案3】:

    我也遇到了同样的问题,这个呢: 值 = INCR(ip) 如果 [ 值 == 1 || PTTL(ip) == -1 ]那么 过期(ip, 1)

    如果 2 个客户端几乎同时进行 PTTL,则得到 -1 并设置几乎同时过期

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-30
      • 2022-01-05
      • 1970-01-01
      • 2012-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多