首页 文章

如何使用Redis创建分布式锁?

提问于
浏览
12

在redis文档中,我发现可以通过SETNX实现原始锁:

http://redis.io/commands/setnx

C4发送SETNX lock.foo以获取锁定崩溃的客户端C3仍然保留它,因此Redis将回复0到C4 . C4发送GET lock.foo以检查锁是否过期 . 如果不是,它会睡一段时间并从头开始重试 . 相反,如果锁定已到期,因为lock.foo上的Unix时间早于当前的Unix时间,C4会尝试执行:GETSET lock.foo由于GETSET语义,C4可以检查存储在key中的旧值是否仍然存在过期的时间戳 . 如果是,则获取锁定 . 如果另一个客户端(例如C5)比C4快并且通过GETSET操作获得锁定,则C4 GETSET操作将返回未过期的时间戳 . C4将从第一步开始重新启动 . 请注意,即使C4将来设置密钥几秒钟,这也不是问题 .

但是,正如一些用户所评论的那样,使用UNIX时间戳作为过期时间需要客户端和服务器的时间完全同步 . 在Redis中创建全局/分布式锁是否有更好的选择?

6 回答

  • 1

    使用 SET 而不是 SETNX . SET 接受过期时间的参数,以秒和毫秒为单位,而不是UNIX时间戳值 .

    仅基于历史原因记录旧的基于SETNX的模式 .

    来自 SETNX description

    注意:从Redis 2.6.12开始,可以使用SET命令创建更简单的锁定原语来获取锁,并使用简单的Lua脚本来释放锁 . 该模式记录在SET命令页面中 .

  • 11

    使用 redis >= 2.6 LUA脚本解决方案会很棒 . Lua脚本总是原子地执行所以:

    --lockscript, parameters: lock_key, lock_timeout
    local lock = redis.call('get', KEYS[1])
    if not lock then    
        return redis.call('setex', KEYS[1], ARGV[1], "locked");
    end
    return false
    

    另一种解决方案基于 SET 命令的新选项

    SET lock_key "locked" EX lock_timeout NX
    

    使用 redis < 2.6 可以使用带有multi的模式:

    MULTI
    SETNX tmp_unique_lock some_value
    EXPIRE tmp_unique_lock
    RENAMENX tmp_unique_lock real_lock
    EXEC
    
  • 3
  • 8

    SET的新参数足以设置锁定,但这些仅适用于Redis> = v2.6.12,您还需要考虑锁定将如何取消设置和到期等 .

    我在工程博客上发了一篇关于distributed locks using Redis的帖子 . 它涵盖了如何可靠地设置和释放锁定的脚本,以及防止验证和死锁 . 我还包括一个用Node.js编写的模块,你可以用它直接开箱即用 .

  • 1

    我掏出了一个很酷的宝石提到的 SET EX NX 解决方案 - simple_redis_lock

    代码很简单,看起来像这样:

    def lock(key, timeout)
      if @redis.set(key, Time.now, nx: true, px: timeout)
        begin
          yield
        ensure
          release key
        end
      end
    end
    
  • 0

    关于Redis锁的好文章:“How to do distributed locking” .

    我认为Redlock算法是一个糟糕的选择,因为它“既不是鱼也不是鸡”:它对于效率优化锁是不必要的重量级和昂贵的,但对于正确性取决于锁的情况它并不足够安全 . 特别是,该算法对时序和系统时钟做出了危险的假设(基本上假设一个同步系统具有有界网络延迟和有限的操作执行时间),如果不满足这些假设,它就违反了安全属性 . 此外,它缺乏用于生成防护令牌的设施(其保护系统免受网络中的长时间延迟或暂停的过程) .

    Taooka修复所有描述的问题 . 它隔离了损坏的客户端,在破碎的客户端访问不断变化的数据之前,没有人可以锁定 .

相关问题