Redis分布式锁(Redlock官方文档的理解)

3,220 阅读6分钟

Redis分布式锁(Redlock官方文档的理解)

我github博客原文

官网解释

分布式锁在许多不同进程下需要对共享资源进行互斥操作的环境下,十分需要

Redis作者提出了 Redlock 算法

开始介绍:

安全和活跃度(可靠性)保障(Safety and Liveness)

需要设计合理分布式锁,并满足基本的保障
1.安全 -> 互斥属性,在任何条件下,只允许一个客户端拿到锁
2.活跃度(可靠性)A -> 死锁检测,即使有获取到锁的客户端崩溃或者不可用,但是最终锁还是能被获取到
3.活跃度(可靠性)B -> 容错,只要大部分的redis节点都存活,客户端就能够获取锁和释放锁

为什么故障恢复的实现还不足够

有个Master + slave
1.客户端A获取锁
2.master 在命令传播给slave前就崩溃了
3.slave这时候还不存在key,所以当它被晋升成master时
4.客户端B尝试获取锁,就被获取到了-> 这时候就不满足redis分布式锁的安全性了

在极端情况下,集群服务发生失败时,多客户端可能同时获取锁

单实例的正确实现

在解决上述问题前,先把基本的redis分布式锁的设计做好

当我们获取锁的时候,执行下面命令行:

SET resource_name my_random_value NX PX 30000
NX表示当key不存在才能设置成功
PX表示超时时间 
my_random_value 需要在所有客户端和获取锁的请求中表示唯一

释放锁,需要带着 my_random_value 作标识然后再del,2个操作作原子,所以推荐使用lua脚本

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

说明

1.这样就能有效的阻止其他客户端生成锁。(应该不用多说明吧。)
2.my_random_value 应该怎么设置,反正容量越小,消耗越小越好,文档上给出是使用clientId加上时间戳或者使用RC4算法
3.expire的时间设置,我们称作锁有效时间,是锁自动释放时间 + 客户端需要在锁时间内执行的事务所需要的时间(在其他客户端获取到锁之前)
= 锁自动释放时间 + 客户端处理业务(锁期间)的时间
考虑到互斥的保证,这个时间窗口应该限制在锁被获取之后

基本操作介绍了,现在可以优化了

Redlock算法

在分布式版本中,我们假设我们有N个redis 的matser,各自独立,没有任何关系(不在一个cluster下),可以部署在不同的服务器或是虚拟机上,假设N设置成5

客户端获取锁的操作:
1. 客户端A获取当前时间戳
2. 客户端A尝试在所有N个redis中获取锁(有序的),使用同样的key和value,这一步中,由于需要遍历去请求多个redis服务(set的命令请求,之前理解成业务的处理时间),可能导致阻塞,需要设置超时时间,假设锁自动释放的时间是10s,那么这个超时时间可以设置在 5-50毫秒范围,这一步,防止客户端在获取锁期间由于redis节点崩溃不可用导致获取锁曹氏
3. 客户端A获取锁时,再获取当前时间戳,与步骤1的时间相减,得到获取锁消耗的时间,在锁有效期内,当且仅当客户端A拿到大部分(至少3)的锁时,分布式锁才可以被正式获取
4. 每次当锁被获取到时(从每个master获取),有效时间可以被设置成初始有效时间减去获取锁消耗的时间 (因为已经获取锁了,所以获取锁的时间不用算进去)
5. 假设在步骤2中客户端A获取不到锁,比如拿不到大部分的锁,或者是锁还没超时,它需要把自己在少部分redis上拿到的锁释放掉

这个算法是异步的吗

这个算法依赖的一个假设:是不同进程的各自的时钟精确率(就是表走得快走得慢,而且误差跟锁释放时间比小得多)一样,而且不(需要)作时间的同步。类似于,现实生活中,每个人都各自使用自己的电脑(以及电脑上的时间),通常也不会有什么问题;
在这个情况下,我们需要更具体的说明互斥规则:它(互斥规则)保证仅仅只有获取锁的哭护短能够在锁的有效时间内结束它的工作,减去某个很小的时间差(几毫秒,用来作补偿)

失败重试(这里指的是获取锁的失败)

当客户端获取不到锁,它应该在之后一个随机时间点重试,这为了避免多个客户端尝试同时获取同一个资源(类似脑裂的情况,大概意思就是竞争了一堆,却发现,没人拿到锁),客户端拿到锁越快(早),脑裂的情况越小(或者重试的需要越小),所以理想情况下,客户端可以同时(多路复用)发送set命令给各个master

释放锁

锁释放步骤很简单,就是把所有master实例上的锁释放,并不需要关心客户端在该实例上有没有成功得到锁

安全讨论

假设一个客户端能够获取大多数实例上的锁,所有实例都会存在一个key,并且有个相同的超时时间,但是这个key的设置的时间点是不一样的(就是set的时候都不一样,因为是顺序执行,总会有时间差),所以key会在不同的时间超时。但是当第一个key被至少设成T1,最后一个key被设成T2(超时时间),我们可以确认第一个key会存在至少 MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT 的时间,而且其他的key会失效的更晚,我们要同时将时间设置成这个
当一个key被set(NX)的时候,其他key就无法被别的客户端set了

----
总的说的就是时间设置还有并发控制

可靠性讨论

1. 自动释放的锁最终还能被获取到;
2. 客户端通常会协助删除那些没获取到的锁(步骤5)、锁获取到并且工作结束的,使得并不需要等待锁超时才能重新获取锁;
3. 客户端需要重试获取锁,为了资源竞争,他们重试的等待时间应该大于需要从大多数实例上获取的时间