先大胆下一个结论。
由于分布式环境的复杂性目前是没有完美的分布式锁实现方案的。无论是基于redis的RedLock还是基于zookeeper的分布式锁。
这一篇主要讨论RedLock
简介
基于redis集群的分布式锁🔐
简单实现
算法1
1 set lock_key fix_value NX
2 expire lock_key time
问题
a 假设client A 执行到步骤1,然后client A挂掉,那么永远无法执行到步骤2了,也就是说形成了死锁-安全性问题
b client A 执行完整个步骤,还未进入临界区,然后因为gc停顿或者其他原因停顿,然后锁自动过期了;client B执行完整个步骤,拿到锁,此时client A和client B都获得了🔐;更加严重的是,client A从阻塞状态恢复了,会去释放掉🔐,导致B的🔐也被释放了-安全性问题
c 单节点的redis存在挂掉的情况-活性问题
d 主备形式的redis集群,当主挂掉之后,备会顶上,但是redis的异步复制的,备顶上可能此时还没有主的数数据-安全性问题
初步解决
a 将步骤1和步骤2合并成一个原子操作
算法2
set lock_key fix_value NX PX time
b 将fix_value 换成 random value
释放锁的操作变成如下
算法3
unlock(key, value){
if(redis.call(“get”, key) == value) then
return redis.call(“del”, key)
else
return 0
}
上述操作是利用lua完成,为原子操作,含义为先查看下lock对应的value是否还是自己set进去的值,如果是则可以释放锁,否则认为锁已经被别人占用
上述方式还是无法解决b的第一个问题,但是可以解决b的第二个问题
c 和 d的问题需要完成巨大的变更才有可能解决
RedLock
算法描述
基于多master的redis集群,保证在大多数节点存活的情况下锁的活性和安全性问题
定义
a 锁的超时时间timeout-client需要在这个给定时间内获取到锁,否则认为是超时的
b 锁的有效时间lock validity time-client获得锁后,在这个时间段后需要释放锁
a是远小于b的
c 锁节点的个数
加锁过程
1 记录下当前时间戳
2 依次向redis集群中执行算法2的操作,对于单个节点来说,如果在a的时间内获得🔐,则认为获得此节点的锁,反之亦然。
3 判断是否获得大多数🔐,并将此时的时间戳减去步骤1的时间戳得到b1,如果b1大于b的时间,则认为获取失败,触发释放锁逻辑,否则认为获取锁成功,锁的时间长度为b的时间减去b1的时间长度。
释放锁过程
依次向每个redis节点发出算法3的逻辑
问题分析
1 b的第一个问题还是无法解决
2 由于时钟跳跃会引发新的问题,某个节点已经被client获取了锁,但是因为时钟跳跃问题,锁迅速的过期了,那么可能存在另一个client来获取锁时,此节点会将锁分配给他,那么等于说一个节点被两个client同时成功获取锁了。
Martin认为
出于效率来使用分布式锁,允许偶尔的🔐失败,仅仅需要简单的分布式锁实现方案就可以了,RedLock的实现偏重
出于正确性来使用分布式锁,redLock会因为这样那样的分布式问题,而无法达到正确性的要求