分布式锁之Redis RedLock算法

一、原始的Redis锁

1、使用setnx对资源上锁,产生的键值是<key,value>,key是随机生成的数字。
2、上锁成功则表示已经获得锁,使用acquire设置超时时间
3、其他进程上锁则返回失败,并且检查锁是否设置超时时间,如果没有设置则设置超时时间。

16位随机数字uuid的选取:

【官方推荐】1、从 /dev/urandom/中取20个byte作为随机数
例如使用RC4加密算法在/dev/urandom中得到一个种子(Seed),然后生成一个伪随机流。
2、也可以用更简单的使用时间戳+客户端编号的方式生成随机数

二、竞态模型场景

为了避免Redis宕机引起锁服务不可用, 需要为Redis实例(master)增加热备(slave),如果master不可用则将slave提升为master。这种主从的配置方式存在一定的安全风险,由于Redis的主从复制是异步进行的, 可能会发生多个客户端同时持有一个锁的现象。

竞态模型:
1、Client A 获得在master节点获得了锁
2、在master将key备份到slave节点之前,master宕机
3、slave 被提升为master
4、Client B 在新的master节点处获得了锁,Client A也持有这个锁。

这导致:master不可用时,它所包含的锁同样不被记录,之前已经获得的锁被视为无效,对于刚升级为master的slave而言,仍可以对某资源加锁。
解决方法:分布式下的RedLock算法

三、多Redis实例解决单点故障-----RedLock算法

一个Client想要获得一个锁需要以下几个操作:

1、得到本地时间

2、Client使用相同的key和随机数,按照顺序在每个Master实例中尝试获得锁。在获得锁的过程中,为每一个锁操作设置一个快速失败时间(如果想要获得一个10秒的锁,那么每一个锁操作的失败时间设为5-50ms)。这样可以避免客户端与一个已经故障的Master通信占用太长时间,通过快速失败的方式尽快的与集群中的其他节点完成锁操作。

3、客户端计算出与master获得锁操作过程中消耗的时间,当且仅当Client获得锁消耗的时间小于锁的存活时间,并且在一半以上的master节点中获得锁。才认为client成功的获得了锁。

4、如果已经获得了锁,Client执行任务的时间窗口是锁的存活时间减去获得锁消耗的时间。

5、如果Client获得锁的数量不足一半以上,或获得锁的时间超时,那么认为获得锁失败。客户端需要尝试在所有的master节点中释放锁, 即使在第二步中没有成功获得该Master节点中的锁,仍要进行释放操作。

可用性分析:
(1)锁的自动释放(key expire),最终锁将被释放并且被再次申请。
(2)客户端在未申请到锁以及申请到锁并完成任务后都将进行释放锁的操作,所以大部分情况下都不需要等待到锁的自动释放期限,其他client即可重新申请到锁。
(3)假设一个Client在大多数Redis实例中申请锁请求所成功花费的时间为Tac。那么如果某个Client第一次没有申请到锁,需要重试之前,必须等待一段时间T。T需要远大于Tac。 因为多个Client同时请求锁资源,他们有可能都无法获得一半以上的锁,导致脑裂双方均失败。设置较久的重试时间是为了减少脑裂产生的概率。

**脑裂(split brain):**
三个Client同时尝试获得锁,分别获得了2,2,1个实例中的锁,三个锁请求全部失败。
一个client在全部Redis实例中完成的申请时间越短,发生脑裂的时间窗口越小。
所以比较理想的做法:同时向N个Redis实例发出异步的SET请求。

猜你喜欢

转载自blog.csdn.net/vainfanfan/article/details/89106275