干货分享:如何让锁变的更加安全?
我们来看下面的例子如下图所示客户端在时间点 S0 尝试去抢锁在时间点 S1 在后端抢锁成功因此也产生了一个分布式锁的有效期窗口。在有效期内时间点 S2 做了一个访问存储的操作很快完成然后在时间点 S3 判断锁的有效期依旧成立继续执行访问存储操作结果这个操作耗时良久超过了分布式锁的过期时间那么可能这个时候分布式锁已经被其他客户端抢占成功进而出现两个客户端同时操作同一批数据的可能性这种可能性是存在的虽然概率很小。针对这个场景我们也是有方案可以应对的在操作数据的时候确保有足够的锁有效期窗口当然如果业务本身提供回滚机制的话那么方案就更加完备该方案也在存储产品使用分布式锁的过程中被采用。不过我们还要继续探讨我们认为的最佳方案存储系统本身引入 IO Fence 能力。这里就不得不提 Martin Kleppmann 和 redis 的作者 antirez 之间的讨论了redis 为了防止异步复制导致的锁丢失的问题引入了 redlock 该方案引入了多数派的机制需要获得多数派的锁最大程度的保证了可用性和正确性但仍然有两个问题墙上时间的不可靠 NTP 时间异构系统的无法做到严格正确性墙上时间可以通过非墙上时间 Monotonic 来解决 redis 目前仍然依赖墙上时间但是异构的设计的只依靠单个系统无法保证完全正确如下图七所示Client1 获取了锁在操作数据的时候发生了 GC 在 GC 完成时候丢失了锁的所有权造成了数据不一致。因此我们需要两个系统同时协作来完成一个完全正确的互斥访问在存储系统引入 IO Fence 能力如下图所示全局锁服务提供全局自增的 token Client1 拿到锁返回的 token 是 33 并带入存储系统发生 GC 当Client2 抢锁成功返回 34 带入存储系统存储系统会拒绝 token 较小的请求那么经过了长时间full gc重新恢复后的 Client 1 再次写入数据的时候因为存储层记录的 Token 已经更新携带 token 值为 33 的请求将被直接拒绝从而达到了数据保护的效果 chubby 的论文中有讲述也是 Martin Kleppmann 提出的解决方案。