RedLock红锁安全性争论(下)

500 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情

RedLock红锁安全性争论(下)

Martin分析RedLock总结

在上篇文章中聊到了分布式专家Martin对于RedLock的分析,他提出RedLock并不能保证安全性,而且系统依赖于时钟的准确性这不是一个优秀的分布式异步模型,Martin对于RedLock有如下总结

  • RedLock分布式锁neither fish nor fowl不伦不类,如果为了效率使用分布式锁那么没有必要,因为Redis单实例会做的更好,如果是因为准确性也不需要使用RedLock因为它不够安全。

  • 依靠时钟的假设不合理,RedLock很多场景需要依赖多个实例的时钟去实现,而时钟是不可靠的,运维人员可以修改时钟或者NTP时钟同步出现跳跃,这就RedLock不安全的重要原因。

  • 无法保证准确性,RedLock没有提供类似fencing token的方案,也就是说在RedLock自动释放锁可能会导致锁过期而客户端依旧在操作共享资源,应该考虑使用类似Zookeeper的方案或者支持事务的数据库。

Martin为上述结论提出下面三种场景假设

假设存在实例A、B、C、D、E以及客户端1和2

场景一

提出RedLock没有类似fencing token的方案

  • 客户端1从这些实例中获取锁成功。

  • 客户端1持有锁后,GC运行,程序停顿。

  • 应用停顿过程中,客户端1持有的锁过期。

  • 客户端2从实例中获取锁成功。

  • 客户端1从停顿中恢复,这时客户端1并未意识到锁已经失效,导致客户端1和客户端2同时操作共享资源。

==注意:步骤2举例是GC造成的程序停顿从而导致安全性问题,这里也有可能是网络阻塞以及时间漂移==

场景二

论证依赖时钟假设不合理

  • 客户端1获取实例A、B、C的锁后,由于网络问题无法访问实例D、E。

  • 客户端1持有锁后,实例C因为时钟漂移向前跳跃导致锁过期。

  • 客户端2获取实例C、E、D的锁后,由于网络问题无法访问实例A、B。

  • 这时客户端1和客户端2持有相同的锁。

**注意:**在步骤2当实例C宕机时,而且锁数据没有持久化到磁盘时,也会出现上面的场景,所以RedLock才会提出延迟重启这个概念,间隔时间大于锁的过期时间,这时如果有时钟漂移问题,那么延迟重启是解决不了的。

场景三

论证网络延迟造成的安全性问题,但需要注意这个场景假设是不合理的因为RedLock有处理

  • 客户端1请求锁定实例A、B、C、D、E。

  • 客户端1在等待所有实例返回的过程中发生GC,进入stop-the-world。

  • GC过程中锁过期。

  • 客户端2请求锁定实例A、B、C、D、E。

  • 客户端1GC完成,并收到实例A、B、C、D、E的响应,表明已经成功获取锁(当进程暂停时它们被保存到客户端1的内核网络缓存区中)。

  • 客户端1和2同时持有锁

Martin提出这些观点后,Redis的作者Antirez在博客上也作出了相应的回应,可以参考antirez.com/news/101,下面…

Antirez对Martin的回应

Antirez总结了Martin的问题,主要是如下两点

  • 带有自动过期功能的分布式锁,必须提供某种fencing方案来保证对共享资源真正的互斥保护,也就是说需要一种方法避免客户端在过期时间后使用锁出现的安全性问题,RedLock提供不了这种机制。

  • RedLock构建在一个不够安全的系统模型上,它对于系统时间假设有较高的要求,而这些要求在现实系统中是无法保证的。

Antirez对于fencing方案的分析

Antirez指出fencing方案是不合理的,fencing方案需要获取一个增量ID,而递增ID要求RedLock是一个可线性化的存储,而在大多数情况下对共享资源的操作并不是可线性化的,而RedLock虽然提供不了fencing方案那种递增的ID,但也可以产生随机字符串,这个随机字符串也是唯一的也就是Antirez文章中提到的unique token,Antirez举例可以实现Check and Set操作,原文如下,稍微有点难理解

For example you can implement Check and Set. When starting to work with a shared resource, we set its state to “<token>”, then we operate the read-modify-write only if the token is still the same when we write.

(译文:例如,您可以实现Check and Set。当开始使用共享资源时,我们将其状态设置为“' <令牌> '”,然后只有当写入时令牌仍然相同时,才操作read-modify-write。)

简单点讲就是自旋去执行CAS操作,只有相同时才更新

另外Antirez重点强调,也是质疑的一点

Martin提出的当锁失效后,客户端继续操作共享资源为了保证互斥,这时可以采用类似fencing的方案来避免,那为什么还需要使用一个分布式锁并且还要要求它有那么强的安全性保证呢?完全可以直接使用fencing方案。

到这里Antirez反驳了Martin提出的fencing方案,RedLock中有其它方案保证。

Antirez对于依赖时间的分析

Antirez首先指出RedLock并不是强依赖时钟,允许有一定误差最大是10%,原文如下

Redlock assumes a semi synchronous system model where different processes can count time at more or less the same “speed”. The different processes don’t need in any way to have a bound error in the absolute time. What they need to do is just, for example, to be able to count 5 seconds with a maximum of 10% error. So one counts actual 4.5 seconds, another 5.5 seconds, and we are fine.

(译文:Redlock采用半同步系统模型,其中不同的进程可以以差不多相同的“速度”计算时间。不同的过程在绝对时间上不需要有任何限制误差。他们需要做的只是,例如,能够以最大10%的误差计算5秒。一个算4.5秒,另一个算5.5秒,没问题。)

Martin担心的时钟漂移问题有两点

  • 系统管理员更改了时钟。

  • ntp时钟同步出现跳跃。

对于Martin的担心Antirez指出

  • 系统管理员更改了时钟,那不这样做就好了,不然Raft持久化日志修改,那么Raft协议也将无法工作。

  • ntp时钟同步跳跃,通过合理运维也能避免。

到这里如果认为Antirez对时钟漂移分析的合理,那么后面的分析也是合情合理。

Antirez首先指出,Martin提出的后面两种场景,其中一种是犯了一个大错的,这就是前面提到的第三个场景,因为GC pause引起,导致锁实例和客户端之间有长时间的消息延迟,这个情况RedLock是能处理的,先回顾下RedLock算法。

  1. 加锁前获取当前时间T1

  2. 对N个实例进行加锁操作全部完成

  3. 获取当前时间T2

  4. 将T2-T1计算耗时是否超过锁的过期时间,如果超过将对所有的实例进行解锁操作

  5. 如果没有过期,那么计算加锁成功个数是否大于等于N/2+1,如果成立,那么加锁成功

在Martin提出的场景中,就是在第2步加锁时客户端与Redis实例中存在长时间的消息延迟,这种场景可以在第4步被检测出来,不会让客户端拿到已经过期的锁,当然这种场景依赖于系统的时钟没有大的跳跃情况下,这也就是为什么Antirez会解释依赖时钟的可行性。

有人指出超时可能发生在第3步后,也就是获取时间T2后,就是Martin提到的第二个场景,Antirez承认会有这个问题,同时给出下面的解释

The delay can only happen after steps 3, resulting into the lock to be considered ok while actually expired, that is, we are back at the first problem Martin identified of distributed locks where the client fails to stop working to the shared resource before the lock validity expires. Let me tell again how this problem is common with all the distributed locks implementations, and how the token as a solution is both unrealistic and can be used with Redlock as well.

(译文:延迟只能发生在第3步之后,这导致锁被认为是有效的而实际上已经过期了,也就是说,我们回到了Martin指出的第一个问题上,客户端没能够在锁的有效性过期之前完成与共享资源的交互。让我再次申明一下,这个问题对于_所有的分布式锁的实现_是普遍存在的,而且基于token的这种解决方案是不切实际的,但也能和Redlock一起用。)

Antirez回答的总结

Redis的作者Antirez根据Martin提出的问题,做出了分析,总结如下

  • 作者同意Martin提出的时钟跳跃的问题,但是这个问题通过正确的运维以及基础设施(硬件性能,芯片材料等)可以避免。

  • 对于Martin提出的NPC问题,如果发生在加锁前那么可以检测加锁耗时也就是前面提到的T2-T1是否大于锁过期时间发现,如果发生在加锁后RedLock确实无法处理,但这并不仅仅是RedLock分布式锁面临的问题,其余分布式锁也存在这个问题,如zookeeper。

NPC:N网络延迟、P进程暂停如GC、C时钟漂移

到这里两位大佬的博客就分析完毕了,如果有机会建议都看看这两篇文章!

博客原文参考

martin.kleppmann.com/2016/02/08/…

antirez.com/news/101

分析参考

mp.weixin.qq.com/s/s8xjm1ZCK…

zhangtielei.com/posts/blog-…