问题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也超过多数派,也会拿到锁。 解决方案- 延迟重启
问题2: 时钟跳跃
刚上面讨论的方案严格依赖时钟,而5台机器上面的时钟是可能有误差的。 时钟跳跃的意思就是:实际时间只过了1s钟(假设),但系统里面2次时间之差可能是1分钟,也就是系统之间发生了跳跃。发生这种情况,可能是运维人员认为修改了系统时间。
时钟跳跃会产生2个后果: (1)延迟重启机制失效。时钟跳跃可能导致机器挂了立马重启,从而出现上面的问题。 (2)时钟跳跃导致客户端拿到锁之后立马失效。endTime - beginTime 差值太大。这虽然不影响正确性,但影响拿锁的效率。
那么时钟回拨呢?endTime - beginTime会成为负值,不影响算法的正确性。
问题3: 客户端大延迟(比如full GC),2个客户端拿到同一把锁。
理论上,一切有超时强制释放机制的锁,都可能产生这个问题。服务端把锁强制释放了,但是客户端的代码并没有执行完,卡在了某个地方(比如full GC,或者其它原因导致进程暂停),这把锁被分配给了另外一个客户端。
针对这个问题,Redis又提出了watch dog机制。大致意思就是,锁快要到期之前,发现客户端业务逻辑还没执行完,就给锁续期,避免锁被强制释放,分配给另外一个客户端。但是,锁续期本身是个网络操作,也没办法保证续期一定成功!
从这个案例中,可以得到2个重要启示:
(1)在分布式系统中,严格依赖每台机器本机时钟的算法,都可能有风险。 (2)一切具有“超时强制释放机制”的锁,都可能导致客户端还在持有锁的情况下,锁被强制释放。
另外一篇博客给出的答案
1、脑裂问题:就是多个客户端同时竞争同一把锁,最后全部失败。
比如有节点1、2、3、4、5,A、B、C同时竞争锁,A获得1、2,B获得3、4,C获得5,最后ABC都没有成功获得锁,没有获得半数以上的锁。官方的建议是尽量同时并发的向所有节点发送获取锁命令。客户端取得大部分Redis实例锁所花费的时间越短,脑裂出现的概率就会越低。 需要强调,当客户端从大多数Redis实例获取锁失败时,应该尽快地释放(部分)已经成功取到的锁,方便别的客户端去获取锁,假如释放锁失败了,就只能等待锁超时释放了
2、假设一共有5个Redis节点:A, B, C, D, E:
- client1锁住A、B、C,没有锁D、E
- C数据没有持久化就崩溃重启
- client2锁住了C、D、E,获取锁成功
解决方案:C崩溃后延时重启,延时时间大于锁的过期时间
3、假设一共有5个Redis节点:A, B, C, D, E:
客户端1从Redis节点A, B, C成功获取了锁(多数节点)。由于网络问题,与D和E通信失败。 节点C上的时钟发生了向前跳跃,导致它上面维护的锁快速过期。 客户端2从Redis节点C, D, E成功获取了同一个资源的锁(多数节点)。 客户端1和客户端2现在都认为自己持有了锁。
选用分布式锁时需要明确用途:
- 提升效率:避免一个任务被执行两次。但是就算执行了两次也不会出现致命问题
- 保证正确:一定要保证数据正确,不允许出现重复执行的情况
· 假如是为了提升效率而使用锁,则使用单机模式就足够了 · 假如是需要保证绝对的正确,redlock并不能达到目的。应该考虑类似Zookeeper的方案,或者支持事务的数据库。
|