聊一聊分布式锁的设计模型
一、什么是分布式锁?
- 实现操作原子性:在单机环境中,为了实现多进程或多线程对共享资源操作过程的原子性,我们可以借助内核提供的 SpinLock 或 Mutex 机制,保证只有一个进程或线程操作共享资源。和单机环境对锁的需求类似,在分布式环境中,我们通常会用分布式锁控制多个机器上的节点并发操作,避免数据或状态被破坏。
- 实现系统高可用:为了服务的高可用,往往需要部署多个节点实现服务冗余,避免单点故障造成的服务不可用。借助分布式锁+服务发现实现的选主功能,节点根据抢锁成功与否决定是否成为主节点对外提供服务。当发生节点宕机时,其他备份节点可以通过争抢到分布式锁的所有权,继续提供访问服务。
二、设计模型
我们再看另一个大名鼎鼎的 Redis 对分布式锁性质的定义,它提出了分布式锁模型必须要遵守的三个原则:a)绝对互斥,同一时刻,只有一个客户端能够持有分布式锁;b)最终可用,如果持有分布式锁的客户端意外退出了,那么相关的分布式锁资源要能够被重新再分配;c)服务容错,提供分布式锁的服务本身要具备容错能力,即使部分节点崩溃,也不影响整体的分布式锁服务。
结合自身的经验,我们高度认同Redis对有关分布式锁模型的基本约束条件,这些其实也是实现一个分布式锁服务所必须要考虑的几个属性。并且,Redis相关的文章中也继续探讨了分布式锁的其它的特性约束。事实上,如下图3所示,我们从三个维度归纳总结一个分布式锁模型落地需要考虑的性质。第一个维度是最基本的约束条件,与Redis提出的完全一致,我们称之为:互斥性,可容错,最终可用;第二层提出的分布式锁管理器需要关注的一些锁的特性,譬如抢锁效率,分布式锁的均衡性,锁的切换精度,锁的可重入性质等等。在这个之上,还有一个分布式锁落地时候必须要考虑的事关数据一致性与正确性保证的约束,即可防护性以及应对好时钟漂移的影响。
图3 分布式锁设计模型需要考虑的三个维度的性质
关于分布式锁管理器实际落地需要考虑的数据一致性与正确性的话题,其中一个话题是墙上时间的不靠谱,这个可以引入非墙上时间MonoticTime来解决,本文就不在这个问题上做更多讨论。另一个话题,实际使用分布式锁服务来访问共享资源的时候一定要辅助以Fencing能力方可做到资源访问的绝对互斥性。大神Martin Kleppmann提供了一个非常好的案例说明,如下图4所示,Client1首先获取了分布式锁的所有权,在操作数据的时候发生了GC,在长时间的"Stop-The-World"的GC过程中丢失了锁的所有权,Client2争抢到了锁所有权,开始操作数据,结果等 Client1的GC完成之后,就会出现Client1,Client2同时操作数据的情形,造成数据不一致。
针对上述问题,解决方案是引入共享资源访问的IO Fence能力,如下图5所示,全局锁服务提供全局自增的 Token,Client1拿到锁返回的Token是33,并带入存储系统,发生 GC,当Client2 抢锁成功返回 Token 34,带入存储系统,存储系统会拒绝后续Token小于34的请求,那么经过了长时间GC重新恢复后的 Client 1再次写入数据的时候,因为底层存储系统记录的Token已经更新,携带Token 值为33的请求会被直接拒绝,从而达到了数据保护的效果。
回到文章的主旨,如何实现一个高效的分布式锁管理器呢?首先,抛出一个观点,分布式锁管理器也可以按照控制平面与数据平面进行切分。图3中提到的分布式锁性质可以划分到不同的平面分别负责。我们的这个观点其实并非首创,事实上在OSDI'20的Best Paper -《Virtual Consensus in Delos》一文,Facebook的研究团队针对一致性协议的设计做了深入探讨,非常的精彩。文章里面提到了类似Raft这类分布式一致性协议,里面也同样可以分拆出管控平面与数据平面,前者负责容错、成员变更、角色调整,后者负责定序与持久化。通过解耦两个平面,一下子让共识协议变得很灵活。
我们分布式锁模型的实现是否也可以参考类似的思路呢?将容错、成员变更等负责的逻辑转移至管控平面,而数据平面负责分布式锁的其它譬如互斥,最终可用,抢锁效率等等功能。答案是肯定的,好吧,即使这样的思路也并非我们首创,在数据库领域,一直有这么个流派来演进这类的分布式锁系统,它们被统称为 DLM(Distributed Lock Manager),典型的有 Oracle RAC,GFS2,OCFS2,GPFS,我们接下来好好说道说道DLM。
三、何谓DLM?
DLM 的思想来自《The VAX/VMS Distributed Lock Manager》,在1984年首次应用于 VAX/VMS V4.0。接下来,我们以 Oracle RAC 为例,来说明下 DLM 的设计思路。 Oracle RAC 运行于集群之上,基于内存融合技术,使得 Oracle 数据库具备高可用性和极致性能。如果集群内的一个节点发生故障,Oracle 可以继续在其余的节点上运行。为了保证多个节点写入内存 Page 过程的一致性,使用分布式锁管理器(DLM)处理分布式锁资源的分配和释放。
如图7所示,DLM 是一个去中心化的设计,集群中的所有节点都是对等的,每个节点都维护了部分锁信息。那么申请锁时,应该由谁来决定锁的分配呢 ? 在 DLM 中,每把锁都有 Master 的概念,由 Master 统一协调、授权,决定是否允许加锁或解锁,每个节点都有可能成为锁的 Master。每个节点管理这些锁资源时,将这些锁资源通过树状结构进行组织,通过对树节点的父子继承关系可以优化锁的粒度,提升加解锁的效率。
在加锁或解锁过程中,涉及到以下几类节点: a)Requester: 发起加锁或解锁的节点;b)DirectoryNode: 锁的目录节点,存放着锁的 Master 被哪个节点锁持有这类信息;d)Master: 锁的持有者,实际管理者,负责锁的分配,释放。下面我们用具体示例来描述分布锁的分配、释放的具体过程,例子里面存在A, B, C 3个节点,其中A 为 Requester,B 为 DirectoryNode, C 为 Master 节点。
- 重建节点拓补:leader 节点通过两阶段投票方式向集群其他节点发起通告,告知当前集群节点拓扑情况,其他节点有权利接受或拒绝该信息。若对该拓扑图未达成一致( 其他节点拒绝该拓扑信息 ),leader 会休眠一段时间,其他节点执行 leader 选举,新 leader 会向其他节点发出通告。通过该方式,实现集群中所有节点对全局拓扑图以及锁资源的路由算法达成一致。在成员变更期间,仍可以发起抢锁请求,但这些请求会在请求队列中,并不能抢锁成功。成员变更完后,这些请求按照发起顺序被重新发出。
- 重建节点锁信息:leader 会通知其他节点对锁信息进行重建,重建过程拆分为多个阶段,当所有节点完成一个阶段后,leader 会通知集群所有节点进入下一个阶段。在重建过程中,任一节点发生故障,均需要重新发起选举和重建流程。重建分为以下阶段: 1) 节点清空目录信息(锁的路由表)以及节点持有的锁,这是因为锁资源信息需要重新路由;2) 对于之前节点持有的锁,按照原来的路由策略和顺序重新发起加锁,这个过程会将整个集群的锁的目录信息重新建立起来,锁的 Master 重新确定。由于每个节点对仅对自身重新加锁,那么对于发生故障被删除节点而言,它之前持有的锁 Master 会被新节点替代;3) 所有节点完成重新加锁流程后,就可以执行正常的加解锁流程了。
四、最佳实践
阿里云存储部门拥有着从块存储到文件存储,对象存储,日志存储,以及表格存储等全球最完整的存储产品体系。图 10 展示了当前存储产品采用的非常通用的基于分区调度模型的系统架构。整个业务系统按照管控平面与数据平面来划分,其中数据平面将用户的存储空间按照一定规则分割成若干分区,在运行时一个分区会被分配至某个服务器提供服务,一个服务器可以同时加载多个分区。分区不使用本机文件系统存储持久化数据,其拥有的全部数据均会存储在盘古分布式文件系统中的特定目录。基于如此的分区调度模型,当某个服务器发生宕机的时候,它承载的分区需要被重新调度,快速迁移至其它健康的服务器继续提供服务。
在云存储的分区调度模型中,有关分区资源的互斥访问(即任何时刻任一个分区必须至多为某一台服务器所加载并提供读写访问服务)是存储系统提供数据一致性的基石,必须得到保障。事实上,云存储的最佳实践中有着类似 DLM 的设计哲学,将分布式锁管理器的容错问题抽离出来,借助女娲-飞天分布式协同基础服务提供的选主功能来实现,进而可以专注在分布式锁资源的调度策略:
1)管控调度器负责具体分区资源的互斥分配,这里结合不同存储业务的特殊需要,可以演进出不同的调度策略,从分布式锁的均衡性,分布式锁的抢锁效率,分布式锁的切换精度等不同维度做专项优化; 2)分布式锁管理器中最复杂的容错能力,通过依赖女娲选主功能实现,并且通过女娲的服务发现能力实现管控节点平滑上下线; 3)存储系统的数据最终均存放在盘古-飞天分布式存储文件系统,从具体的分区数据,到管控调度的元数据,这些信息都会放入盘古。盘古提供了高可靠高性能的存储服务以及 Fencing 保护能力,保障数据一致性;
分布式锁提供 Fencing 保护的核心点是在访问共享资源的时候带上 Token 检查。盘古作为存储的统一的基座,通过引入特殊的 InlineFile 文件类型,配合 SealFile 操作,实现了类似的 IO Fence 保护能力:a)SealFile 操作用来关闭已经打开的文件,防止分布式锁旧的占有者继续写数据;b)为每个分区引入 InlineFile,针对盘古文件的元数据操作关联 InlineFile 相关的 CAS 判断,进而可以防止分布式锁旧的占有者打开新的文件。如图 11 所示,这两块功能结合起来事实上也是提供了存储系统中的写数据 Token 检查支持。
我们看到,就云存储的 DLM 实现中,有一个通用的基于分区的调度器,有女娲提供容错保障,由盘古提供资源的 Fencing 保护,这个就是云存储的最佳实践。五、总结
分布式锁提供了分布式环境下共享资源的互斥访问,在分布式系统中应用十分广泛。这篇文章从分布式锁的性质出发,探讨了分布式锁的模型设计。就分布式锁系统,我们探讨了控制平面与数据平面解耦的架构设计,并介绍了阿里云存储场景下分布式锁的最佳实践。期望我们的分享对于读者朋友有所帮助。
参考文献
1. How to do distributed locking -- Martin Kleppmann:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html 2. Distributed Lock Manager -- Apache Helix:https://helix.apache.org/1.0.2-docs/recipes/lock_manager.html 3. Distributed Locks with Redis -- Redis:https://redis.io/docs/reference/patterns/distributed-locks/ 4. The VAX/VMS Distributed Lock Manager -- VMS Software:https://wiki.vmssoftware.com/Distributed_Lock_Manager 5. Cache Fusion: Extending Shared-Disk Clusters with Shared Caches -- Oracle RAC:http://www.dia.uniroma3.it/~vldbproc/086_683.pdf往期推荐