分布式锁 - RedLock到底有哪些缺陷?
在上篇中,已经说了分布式锁是个“大坑”,这里就来详细分析一下,RedLock究竟有哪些缺陷,为啥会引发Redis作者和另外一位分布式大牛的激烈辩论。
RedLock的基本思路就是为锁准备多个副本,避免Redis主从切换的时候,数据丢失。
1. RedLock算法详细解释
(1)跟ZK一样,多数派的思路。假设部署了5台Redis,获取锁的时候,从5台机器获取,只要超过半数(其中3台)获取到锁,就算获取锁成功。允许最多2台宕机。
(2)锁必须有一个超时强制释放机制,假设10秒。避免客户端宕机,锁永远无法释放。
(3)取每台机器的锁之前,记一下当前时间beginTime;取锁之后,再记一下当前时间endTime。
if endTime - beginTime > TTL(10秒),意味着刚获取到锁,就已经过期了,获取锁失败。
(4)如果获取锁失败,也就是没有超过半数,则在所有Redis节点上执行释放锁操作。(为什么是释放所有节点,而不是只释放成功的那些节点?还是前面说的网络2将军问题,未成功的那些节点,可能只是返回客户端的时候超时了,但实际已经加了锁)
(5)如果第(4)步里面释放节点失败,只能依赖第(2)步里面,锁的强制释放机制。
2. 表面看起来,这个算法蛮完美,解决了前面单机版Redis分布式锁的缺陷,那还存在什么问题呢?
问题1: 宕机重启之后,2个客户端拿到同一把锁。- 延迟重启
假设5个节点是A, B, C, D, E,客户端1在A, B, C上面拿到锁,D, E没有拿到锁,客户端1拿锁成功。 此时,C挂了重启,C上面锁的数据丢失(假设机器断电,数据还没来得及刷盘)。客户端2去取锁,从C, D, E 3个节点拿到锁,A, B没有拿到(还被客户端1持有),客户端2也超过多数派,也会拿到锁。
为此,Redis作者提出了延迟重启的办法:重启的时候,不立马重启,等待TTL时间之后再重启,保证这台机器此前参与的那些锁,全部过期,一笔勾销。
问题2: 时钟跳跃
刚上面讨论的方案严格依赖时钟,而5台机器上面的时钟是可能有误差的。
时钟跳跃的意思就是:实际时间只过了1s钟(假设),但系统里面2次时间之差可能是1分钟,也就是系统之间发生了跳跃。发生这种情况,可能是运维人员认为修改了系统时间。
时钟跳跃会产生2个后果:
(1)延迟重启机制失效。时钟跳跃可能导致机器挂了立马重启,从而出现上面的问题。
(2)时钟跳跃导致客户端拿到锁之后立马失效。endTime - beginTime 差值太大。这虽然不影响正确性,但影响拿锁的效率。
那么时钟回拨呢?endTime - beginTime会成为负值,不影响算法的正确性。
问题3: 客户端大延迟(比如full GC),2个客户端拿到同一把锁。
理论上,一切有超时强制释放机制的锁,都可能产生这个问题。服务端把锁强制释放了,但是客户端的代码并没有执行完,卡在了某个地方(比如full GC,或者其它原因导致进程暂停),这把锁被分配给了另外一个客户端。
针对这个问题,Redis又提出了watch dog机制。大致意思就是,锁快要到期之前,发现客户端业务逻辑还没执行完,就给锁续期,避免锁被强制释放,分配给另外一个客户端。但是,锁续期本身是个网络操作,也没办法保证续期一定成功!
从这个案例中,可以得到2个重要启示:
(1)在分布式系统中,严格依赖每台机器本机时钟的算法,都可能有风险。
(2)一切具有“超时强制释放机制”的锁,都可能导致客户端还在持有锁的情况下,锁被强制释放。