什么是分布式锁?
什么是分布式锁?对于这个问题,相信很多同学是即熟悉又陌生。随着分布式系统的快速发展与广泛应用,针对共享资源的互斥访问也成为了很多业务必须要面对的需求,这个场景下人们通常会引入分布式锁来解决问题。我们通常会使用怎么样的分布锁服务呢?有开源的 MySQL,Redis,ZooKeeper,Etcd 等三方组件可供选择,总的来看,我们对分布式锁的需求可以大体划分为以下两类应用场景:
- 实现操作原子性:在单机环境中,为了实现多进程或多线程对共享资源操作过程的原子性,我们可以借助内核提供的 SpinLock 或 Mutex 机制,保证只有一个进程或线程操作共享资源。和单机环境对锁的需求类似,在分布式环境中,我们通常会用分布式锁控制多个机器上的节点并发操作,避免数据或状态被破坏。
- 实现系统高可用:为了服务的高可用,往往需要部署多个节点实现服务冗余,避免单点故障造成的服务不可用。借助分布式锁+服务发现实现的选主功能,节点根据抢锁成功与否决定是否成为主节点对外提供服务。当发生节点宕机时,其他备份节点可以通过争抢到分布式锁的所有权,继续提供访问服务。
分布式锁的业务需求、场景看起来比较简单,但是事实上我们在使用分布式锁过程中,总还是会提出这样、那样的新需求,看起来找不到一个分布式锁场景的大一统的解决方案。那么,分布式锁内部究竟是怎么实现的?或者说应该怎么实现呢?这个是我们这篇文章希望探讨的,也希望我们的探讨能够让读者朋友对分布式锁的原理有一定了解,在做技术选型的时候,也能够有更多的指导。
设计模型
我们应该给分布式锁建立怎么样的设计模型呢?这个问题可以换个视角来看,我们应该建立怎么样的合理性质来打造出一个分布式锁模型?
我们不妨参考一下来自业界的两个定义。
Apache Helix
首先是 Apache Helix(开源社区一个风头正劲的通用集群资源管理框架,它能被用作自动管理存在于集群节点上的分区的,有副本的分布式资源)对于分布式锁管理器的性质定义:
- 均匀分布,不是先开始的节点获取所有的分布式锁
- 再调度的均衡性,需要妥善处理持有分布式锁的节点意外退出后的锁资源分配问题
- 再平衡,当有新的节点加入的时候,节点间的锁资源应该再分配以达到均衡。
看的出来,Helix 对分布式锁模型的定义非常强调均衡性,考虑到它是负责集群内的分区资源调度的,这个侧重点并不让人意外。
Redis
我们再看另一个大名鼎鼎的 Redis 对分布式锁性质的定义,它提出了分布式锁模型必须要遵守的三个原则:
- 绝对互斥,同一时刻,只有一个客户端能够持有分布式锁
- 最终可用,如果持有分布式锁的客户端意外退出了,那么相关的分布式锁资源要能够被重新再分配
- 服务容错,提供分布式锁的服务本身要具备容错能力,即使部分节点崩溃,也不影响整体的分布式锁服务。
结合自身的经验,我高度认同Redis对有关分布式锁模型的基本约束条件,这些其实也是实现一个分布式锁服务所必须要考虑的几个属性。并且,Redis相关的文章中也继续探讨了分布式锁的其它的特性约束。事实上,如下图所示,我们从三个维度归纳总结一个分布式锁模型落地需要考虑的性质。
- 第一个维度是最基本的约束条件,与Redis提出的完全一致,我们称之为:互斥性,可容错,最终可用
- 第二层提出的分布式锁管理器需要关注的一些锁的特性,譬如抢锁效率,分布式锁的均衡性,锁的切换精度,锁的可重入性质等等
- 在这个之上,还有一个分布式锁落地时候必须要考虑的事关数据一致性与正确性保证的约束,即可防护性以及应对好时钟漂移的影响
关于分布式锁管理器实际落地需要考虑的数据一致性与正确性的话题,首先,抛出一个观点,分布式锁管理器也可以按照控制平面与数据平面进行切分。上图中提到的分布式锁性质可以划分到不同的平面分别负责。我们的这个观点其实并非首创,事实上Facebook的研究团队针对一致性协议的设计做了深入探讨,非常的精彩。文章里面提到了类似Raft这类分布式一致性协议,里面也同样可以分拆出管控平面与数据平面,前者负责容错、成员变更、角色调整,后者负责定序与持久化。通过解耦两个平面,一下子让共识协议变得很灵活。
我们分布式锁模型的实现是否也可以参考类似的思路呢?将容错、成员变更等负责的逻辑转移至管控平面,而数据平面负责分布式锁的其它譬如互斥,最终可用,抢锁效率等等功能,答案是肯定的。