redis 分布式锁

江枫雨发布

多客户端需要互斥访问(包括读写) redis 共享资源时,需要加分布式锁防止访问冲突。分布式锁应该至少保证以下属性:

  1. Safety 属性:访问互斥,同一时刻只有一个客户端能获得锁。
  2. Liveness 属性:
    • 避免死锁:即使已获得锁的客户端 crash,也会最终释放锁资源。
    • 容错能力:只要大多数(超过半数)的 redis 服务节点能启动成功,客户端就能获得锁。

首先看只有单个 redis 服务节点的场景,要实现资源锁很简单。大致思路是,尝试写入一个 key,如果不存在且写入成功,则表示获得锁成功。为了避免死锁,需要设置 key 的 TTL(time to live),保证即使获得锁的客户端 crash 没有主动释放锁,锁资源也不会被永久占用。redis 对应实现如下:

set key {random_value} nx px 10000

上述命令设置 key 的 TTL 时间为 10s,仅当不存在时才设置。{random_value} 是一个随机数,可以通过 /dev/urandom 来生成,或者当前的 timestamp 加 redis 客户端 ID 来设置。
这里 key 的值设为 random_value 而不是 bool(0/1) 的原因是,防止客户端释放由于过期已经被其他客户端获取的锁资源(比如客户端 A 获得锁,锁因为超过 TTL,客户端 B 获得锁。如果 A 客户端直接通过 del key 释放锁会出现误释放,因而需要一个 random_value 来标识)。
对应释放锁的 lua 代码:

-- 判断下释放的锁资源是否为自身获得的锁资源,防止误释放。
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

单节点存在的问题

当出现节点故障时,没有备份节点会导致整个服务不可用。如果用主从模式,当 master 主节点 crash 时,将资源锁备份到 slave 从节点, 看似提高了稳定性。实际主从模式会违背 Safety 属性的访问互质。由于 master 到 slave 的备份是异步进行的,会出现以下情况:

  1. 客户端 A 从 master 获得锁。
  2. 在锁备份到 slave 节点之前,master crash。
  3. slave 节点成为 master。
  4. 客户端 B 从新的 master 节点依然可以获取锁资源,出现客户端 A 和 B 同时获得锁错误。

分布式锁实现

考虑多个独立 master 节点的情况,假设多个 master 节点分布在不同的机器或虚拟机上,各自间的 failover 互补影响。假设有 N 个 master 节点,引入一个名为 RedLock 的分布式锁算法:

  1. 客户端记录当前时间的 timestamp。
  2. 客户端使用同一个 key 和 random_value 顺序请求 N 个节点获取锁,客户端请求设置一个远小于锁 TTL 的 request_timeout 超时时间,处理节点连接失败的情况,能继续尝试下一节点。比如 TTL 设置为 10s 时,request_timeout 可设置为 10-50ms 等。
  3. 客户端当前时间和 1 中获取的时间差(acquire_elapsed),只有当 acquire_elapsed < TTL 时间,并且客户端成功设置超过半数(N/2 + 1) 个节点时,锁获取成功。
  4. 当客户端成功获取锁后,锁的有效时间为 TTL - acquire_elapsed。
  5. 如果客户端获取锁失败(设置成功的节点数少于一半,或者设置时间差超过 TTL),回退释放已经成功设置的节点锁。

资源竞争问题

RedLock 算法,在多个客户端并发请求锁时,可能会出现饥饿现象,所有的客户端都只获得了部分资源锁(< N/2 + 1)。可通过错开客户端请求,客户端请求设置随机的 retry 频率来解决。

单点故障

考虑某个已分配锁的节点出现 crash 重启,如果 redis 设置文件持久化方式为异步的,则会出现 key 在节点上写入后还未持久化到磁盘,重启之后 key 不存在情况,可能会出现多个客户端能同时获的锁。如果设置持久化方式为同步的,可以解决问题,但严重影响 redis 性能。
解决方法是,如果某个节点重启,设置节点至少超过 TTL 时间之后才可重启继续使用(延迟重启),这样保证已分配的锁,不会因为该节点的重新加入导致可继续分配。延迟重启方式,在不做任何持久化保证情况下解决了问题。当然也引入副作用,如果超过半数节点出现 crash,整个服务至少在 TTL 时间之后才可用。

参考

  1. Redis distributed lock
  2. GO redlock
分类: redis分布式

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注