基于Redis实现分布式锁

用单实例的正确实现

命令:set resource_name my_random_value NX PX timeout_millis,在 key resource_name 不存在时(NX选项的作用)设置这个key 的值为 my_random_value、超时时间设为 timeout_millisPX选项的作用)。

删除key的时候用 Lua 脚本来检测key的值是否为 my_random_value,是才允许删除;需要保证这个值在所有客户端里唯一;(借助 Lua 实现一个 CAS 操作)

一些注意点与问题

  • 一个分布式锁必须设置超时时间,否则一旦某个客户端获取锁成功后与 Redis 无法通信,那么它会一直持有这个锁,其他客户端无法获得锁。
    这个过期时间叫做锁的有效时间(lock validity time),客户端必须在这个时间内完成对资源的访问操作。

  • 设置key与超时时间必须在一个命令里完成。如果客户端在设置了key后、设置超时时间前崩溃,那么它会一直持有这个锁。

  • 释放锁时必须检查key对应的值是否与自己持有的一致(通过 Lua 实现CAS),防止误删其他客户端持有的锁。防止出现:客户端A准备释放持有的锁时,检测到值与自己持有的一致,然后由于某种原因被长时间阻塞,锁的超时时间到达后客户端B获取了锁,此时客户端A执行不检查key值的删除操作就会破坏了锁。

  • 异步主从复制问题。Redis的复制是异步的,在主从模式下,客户端A在 master 节点拿到了锁,master 节点在把A创建的锁的key写入slave之前宕机了,slave变成了master,客户端B从新的master拿到了和A持有的相同的锁(因为原来的slave里还没有A持有的锁的信息),这种情况是单节点实现无法解决的。

  • 锁有效时间的设置多长比较合适?如果设置太短,锁可能在客户端完成对资源的访问之前就过期,从而失去保护;如果设置太长,一旦某个持有锁的客户端释放锁失败,那么就会导致所有其他客户端都无法获取锁,从而使系统长时间无法正常工作。

  • 客户端A再获得锁后发生了阻塞(比如因为GC、CPU竞争、虚存的缺页故障、网络延时等),它获得的锁过期了,客户端B获得了锁;此时A从阻塞中恢复,它不知道自己持有的锁已经过期,依然向共享资源发起写数据请求,而此时锁实际是被B持有,A、B的写请求就有可能冲突。Martin给的方法是 fencing token–一个单调递增的数字,当客户端获取锁成功后随同锁一起返回给客户端。客户端在访问共享资源时带着这个token,这样共享资源的字符就能对它进行检查,可以拒绝掉延迟到来的访问请求(避免冲突)。

RedLock 算法

假定有5个 Redis master 节点,RedLock算法步骤如下:
1. 获取当前时间(单位毫秒);
2. 轮流用相同的key和随机值在N个节点上请求锁,在这一步,客户端在每个master上请求锁时,会有一个比总的锁释放时间小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这样可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点
3. 客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(这里是3),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
4. 如果锁获取成功,那现在锁自动释放的时间就是最初的锁释放时间减去当前获取锁所消耗的时间。
5. 如果锁获取失败了,不管是因为获取成功的锁不超过一半还是因为消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些认为没有获取成功的锁。

失败的重试

当一个客户端获取锁失败时,这个客户端应该在一个随机延时后进行重试,采用随机延时是为了避免不同客户端同时重试导致谁都无法拿到锁的情况出现。

客户端在大多数Redis节点请求锁的时间越短,出现多个客户端同时竞争锁和重试的时间窗口就越小、可能性越低。(通过并发请求多个节点的锁减小获取锁的时间窗口,这样多个客户端获取锁的时间窗口出现交叉的可能性就小了)。

一旦没有获取到多数节点的锁,一定要尽快释放获取成功的锁。

Martin 对 Redlock 的看法

Redlock 是建立在网络延迟有边界、操作的执行时间有边界的同步系统基础的假设上的;当这些条件不满足的时候就会违背锁的安全性。

参考阅读

how-to-do-distributed-locking
Reids 作者对 Martin 的回复
Hacker News 上的讨论


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

发表回复

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

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据