1.基于Redis的分布式锁到底安全吗?
在服务器宕机,或者网络延时过高的时候,redis分布式锁会出现不安全的情况。
情景1: 主从模式下
客户端1在master中获得了锁,
在锁同步到slave之前,master宕机,还未来得及将锁同步到slave
slave升级为master
客户端2在新的master中获取了锁
情景2:网络延时过长,客户端与其他资源交互时间过长
客户端1获取锁,因 网络延时,客户端长时间阻塞 ,锁过期,这时客户端2获取锁,与此同时客户端1的网络恢复正常,这就导致两个客户端可同时访问共享资源。
2. Redlock
基于上述情况,新的分布式锁的算法Redlock
运行Redlock算法的客户端依次执行下面各个步骤,来完成获取锁的操作:
- 获取当前时间(毫秒数)。
- 按顺序依次向N个Redis节点执行获取锁的操作。这个获取操作跟前面基于 单Redis节点的获取锁的过程相同,包含随机字符串my_random_value,也包含过期时间(比如PX 30000,即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个获取锁的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有(注:Redlock原文中这里只提到了Redis节点不可用的情况,但也应该包含其它的失败情况)。
- 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。
- 如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。
- 如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起释放锁的操作(即前面介绍的Redis Lua脚本)。
三、Redlock存在的问题
由于N个Redis节点中的大多数能正常工作就能保证Redlock正常工作,因此理论上它的可用性更高。 我们前面讨论的单Redis节点的分布式锁在failover的时候锁失效的问题,在Redlock中不存在了(解决了遗留问题1) ,但如果有节点发生崩溃重启,还是会对锁的安全性有影响的。具体的影响程度跟Redis对数据的持久化程度有关。
根据上述提出的算法, 当N个节点中有一个节点宕机, 仍然存在锁的安全性问题。具体的影响跟redis的持久化程度有关
假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:
客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。
节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
节点C重启后,客户端2锁住了C, D, E,获取锁成功。
这样,客户端1和客户端2同时获得了锁(针对同一资源)。
其它问题
1、客户端长时间阻塞,导致获得的锁释放,访问的共享资源不受保护的问题。
2、在Redlock的算法中,我们可以看到第3步,当获取锁耗时太多,留给客户端的访问共享资源的时间很短,这种情况若来不及操作,是不是要释放锁呢?且到底剩下多少时间才算短?这又是一个选择难题。
3、Redlock算法对时钟依赖性太强, 若N个节点中的某个节点发生 时间跳跃 ,也可能会引此而引发锁安全性问题。