三个条件
要实现分布式锁,我们需要满足三个条件:
- 互斥性:任一时刻,最多只能有一个client获取到该锁。
- 锁可用:即使获取到锁的client进程崩溃了,也能够最终释放该锁,以使其他client有机会再获取到该锁。
- 容错性:针对redis的集群方式,即使某个master挂掉了,锁也应该是可用的。
简单分布式锁
所谓的简单分布式锁是基于一个redis 实例的。
lock:
set resource_name my_unique_string NX PX 2000
unlock:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
解释:
resource_name是锁的临界资源的名称。
my_unique_string是客户端每次进行lock时生成的一个不会重复的字符串,它的作用是防止client A获取的锁被client B释放。举个例子:假如没有这个my_unique_string。client A 获取到锁资源,之后client A阻塞住,然后该key过期,此时client B重新获取到这个锁,接着client A执行完之前的逻辑并将锁释放。由于没有这个unique_string导致client A将client B的锁释放掉。
缺点:该锁能够满足三个条件的前两个,但是一旦redis节点挂掉,整个锁就不可用了,因此不能满足第三点。
基于redis自动故障转移的简单分布式锁
方案同上。不同的地方是redis实例能够自动故障转移。即一旦master挂掉,原先这个master节点的一个slave节点可以变成master重新提供服务。能够缓解上边的问题。但是,由于master跟slave节点之间的数据同步是异步的,延迟的。即使切换,也可能导致之前master上已经获取到的锁在新的master上是无锁状态,从而出现问题。
RedLock
RedLock的方案采用N个相互独立的master节点。client想要获取锁时,必须获取到N/2+1个master节点的锁方可。
client加锁的过程如下:
- client获取当前的时间
- client顺序的获取N个master的锁。获取每个master节点的锁的过程都有一个超时时间,一旦超过该时间则认为获取失败,然后尝试下一个master。每个锁的过期时间和my_unique_string是相同的。
- client计算整个获取锁的过程的时间是否大于锁的过期时间。如果不大于且获取到锁的master个数>=N/2+1,则认为锁获取成功。
- 如果锁获取成功,那么该锁的剩余有效时间是过期时间-获取锁的时间
- 如果锁失败,则unlock全部的master节点(包含获取失败的master)。
参考资料: