聊一聊分布式锁的设计模型

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 本文介绍了分布式锁的设计模型、运行原理以及具体用法,作者也在文中体现了自己的关于分布式锁的思考以及具体实践。

一、什么是分布式锁?


什么是分布式锁?对于这个问题,相信很多同学是既熟悉又陌生。随着分布式系统的快速发展与广泛应用,针对共享资源的互斥访问也成为了很多业务必须要面对的需求,这个场景下人们通常会引入分布式锁来解决问题。我们通常会使用怎么样的分布锁服务呢?有开源的 MySQL,Redis,ZooKeeper,Etcd 等三方组件可供选择,当然也有集团内自研的 Tair,Nuwa 等分布式锁服务提供方。总的来看,我们对分布式锁的需求可以大体划分为以下两类应用场景:



  • 实现操作原子性:在单机环境中,为了实现多进程或多线程对共享资源操作过程的原子性,我们可以借助内核提供的 SpinLock 或 Mutex 机制,保证只有一个进程或线程操作共享资源。和单机环境对锁的需求类似,在分布式环境中,我们通常会用分布式锁控制多个机器上的节点并发操作,避免数据或状态被破坏。


  • 实现系统高可用:为了服务的高可用,往往需要部署多个节点实现服务冗余,避免单点故障造成的服务不可用。借助分布式锁+服务发现实现的选主功能,节点根据抢锁成功与否决定是否成为主节点对外提供服务。当发生节点宕机时,其他备份节点可以通过争抢到分布式锁的所有权,继续提供访问服务。


分布式锁的业务需求、场景看起来比较简单,但是事实上我们在使用分布式锁过程中,总还是会提出这样、那样的新需求,看起来找不到一个分布式锁场景的大一统的解决方案。那么,分布式锁内部究竟是怎么实现的?或者说应该怎么实现呢?这个是我们这篇文章希望探讨的,也希望我们的探讨能够让读者朋友对分布式锁的原理有一定了解,在做技术选型的时候,也能够有更多的指导。



二、设计模型


我们应该给分布式锁建立怎么样的设计模型呢?这个问题可以换个视角来看,我们应该建立怎么样的合理性质来打造出一个分布式锁模型?我们不妨参考一下来自业界的两个定义。首先是 Apache Helix(开源社区一个风头正劲的通用集群资源管理框架,它能被用作自动管理存在于集群节点上的分区的,有副本的分布式资源)对于分布式锁管理器的性质定义:a)均匀分布,不是先开始的节点获取所有的分布式锁;b)再调度的均衡性,需要妥善处理持有分布式锁的节点意外退出后的锁资源分配问题;c)再平衡,当有新的节点加入的时候,节点间的锁资源应该再分配以达到均衡。看得出来,Helix 对分布式锁模型的定义非常强调均衡性,考虑到它是负责集群内的分区资源调度的,这个侧重点并不让人意外。

image.png 图1 Helix 提出的分布式锁管理器的性质


我们再看另一个大名鼎鼎的 Redis 对分布式锁性质的定义,它提出了分布式锁模型必须要遵守的三个原则:a)绝对互斥,同一时刻,只有一个客户端能够持有分布式锁;b)最终可用,如果持有分布式锁的客户端意外退出了,那么相关的分布式锁资源要能够被重新再分配;c)服务容错,提供分布式锁的服务本身要具备容错能力,即使部分节点崩溃,也不影响整体的分布式锁服务。

image.png

图2 Redis 提出的分布式锁管理器的性质


结合自身的经验,我们高度认同Redis对有关分布式锁模型的基本约束条件,这些其实也是实现一个分布式锁服务所必须要考虑的几个属性。并且,Redis相关的文章中也继续探讨了分布式锁的其它的特性约束。事实上,如下图3所示,我们从三个维度归纳总结一个分布式锁模型落地需要考虑的性质。第一个维度是最基本的约束条件,与Redis提出的完全一致,我们称之为:互斥性,可容错,最终可用;第二层提出的分布式锁管理器需要关注的一些锁的特性,譬如抢锁效率,分布式锁的均衡性,锁的切换精度,锁的可重入性质等等。在这个之上,还有一个分布式锁落地时候必须要考虑的事关数据一致性与正确性保证的约束,即可防护性以及应对好时钟漂移的影响。

image.png


关于分布式锁管理器实际落地需要考虑的数据一致性与正确性的话题,其中一个话题是墙上时间的不靠谱,这个可以引入非墙上时间MonoticTime来解决,本文就不在这个问题上做更多讨论。另一个话题,实际使用分布式锁服务来访问共享资源的时候一定要辅助以Fencing能力方可做到资源访问的绝对互斥性。大神Martin Kleppmann提供了一个非常好的案例说明,如下图4所示,Client1首先获取了分布式锁的所有权,在操作数据的时候发生了GC,在长时间的"Stop-The-World"的GC过程中丢失了锁的所有权,Client2争抢到了锁所有权,开始操作数据,结果等 Client1的GC完成之后,就会出现Client1,Client2同时操作数据的情形,造成数据不一致。

image.png

图4 缺乏Fencing保护的分布式锁可能导致数据不一致


针对上述问题,解决方案是引入共享资源访问的IO Fence能力,如下图5所示,全局锁服务提供全局自增的 Token,Client1拿到锁返回的Token是33,并带入存储系统,发生 GC,当Client2 抢锁成功返回 Token 34,带入存储系统,存储系统会拒绝后续Token小于34的请求,那么经过了长时间GC重新恢复后的 Client 1再次写入数据的时候,因为底层存储系统记录的Token已经更新,携带Token 值为33的请求会被直接拒绝,从而达到了数据保护的效果。


image.png

图5 基于 Fencing 的数据一致性保障


回到文章的主旨,如何实现一个高效的分布式锁管理器呢?首先,抛出一个观点,分布式锁管理器也可以按照控制平面与数据平面进行切分。图3中提到的分布式锁性质可以划分到不同的平面分别负责。我们的这个观点其实并非首创,事实上在OSDI'20的Best Paper -《Virtual Consensus in Delos》一文,Facebook的研究团队针对一致性协议的设计做了深入探讨,非常的精彩。文章里面提到了类似Raft这类分布式一致性协议,里面也同样可以分拆出管控平面与数据平面,前者负责容错、成员变更、角色调整,后者负责定序与持久化。通过解耦两个平面,一下子让共识协议变得很灵活。


image.png

图6 Delos 中 Virtual Consensus 对管控数据平面的观点


我们分布式锁模型的实现是否也可以参考类似的思路呢?将容错、成员变更等负责的逻辑转移至管控平面,而数据平面负责分布式锁的其它譬如互斥,最终可用,抢锁效率等等功能。答案是肯定的,好吧,即使这样的思路也并非我们首创,在数据库领域,一直有这么个流派来演进这类的分布式锁系统,它们被统称为 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。每个节点管理这些锁资源时,将这些锁资源通过树状结构进行组织,通过对树节点的父子继承关系可以优化锁的粒度,提升加解锁的效率。


image.png

图7 DLM的分布式锁角色关系


在加锁或解锁过程中,涉及到以下几类节点: a)Requester: 发起加锁或解锁的节点;b)DirectoryNode: 锁的目录节点,存放着锁的 Master 被哪个节点锁持有这类信息;d)Master: 锁的持有者,实际管理者,负责锁的分配,释放。下面我们用具体示例来描述分布锁的分配、释放的具体过程,例子里面存在A, B, C 3个节点,其中A 为 Requester,B 为 DirectoryNode, C 为 Master 节点。


3.1 加锁过程


图 8 是需要到其他节点上加锁的过程,是所有加锁情况中最耗时的情况,最多需要 2 轮交互。当资源在本地建立后,后续对于具有继承关系的资源在本地加锁就可以了,无需和其他节点进行交互:


1. 节点 A 对资源 R1 加锁,首先在本地构造该锁对象,也称为锁的 shadow,但此时 A 节点并未加锁成功;

2. 节点 A,对资源 R1 通过哈希计算出 R1 对应的目录管理者为节点 B;

3. 节点 A 请求节点 B,节点 B 的记录上显示 R1 的锁的 Master 在 C 上;

4. 节点 A 向节点 C 发起对 R1 加锁请求;

5. 节点 C 维护 R1 的锁请求队列,如果允许 A 加锁,则返回成功;

6. A 更新本地 R1 锁 shadow 相关信息,加锁完成。


image.png

图8 DLM的加锁过程




3.2 解锁过程


图 9 展示了解锁的过程,也比较直观,如下三个步骤:


1. 节点 A 对资源 R1 解锁,删除本地构造该锁对象;

2. 节点 A 请求节点 C,请求将 A 的锁释放;

3. 若 A 是队列中最后一个请求者,则节点 C 将发送请求给 B,将  R1 从目录中摘除,以便后续其他节点能够成为锁的 Master ,否则,C 节点仅仅将 A 从 R1 的加锁队列移除。

image.png

图9 DLM的解锁过程


3.3 成员变更


上述的加锁和解锁过程,仅仅是普通的一次加解锁过程。那么集群出现节点故障、集群增删节点,如何控制分布式锁能够被正常路由和分配呢?在 DLM 中,存在 Connection Manager 角色,除了负责各个节点的网络通信,还有一个重要功能是在集群节点发生增删时,节点间首先选举出 leader 节点进行协调,每个节点均有可能成为 leader 节点。在发生节点增加或删除时会下述过程:


  • 重建节点拓补:leader 节点通过两阶段投票方式向集群其他节点发起通告,告知当前集群节点拓扑情况,其他节点有权利接受或拒绝该信息。若对该拓扑图未达成一致( 其他节点拒绝该拓扑信息 ),leader 会休眠一段时间,其他节点执行 leader 选举,新 leader 会向其他节点发出通告。通过该方式,实现集群中所有节点对全局拓扑图以及锁资源的路由算法达成一致。在成员变更期间,仍可以发起抢锁请求,但这些请求会在请求队列中,并不能抢锁成功。成员变更完后,这些请求按照发起顺序被重新发出。

  • 重建节点锁信息:leader 会通知其他节点对锁信息进行重建,重建过程拆分为多个阶段,当所有节点完成一个阶段后,leader 会通知集群所有节点进入下一个阶段。在重建过程中,任一节点发生故障,均需要重新发起选举和重建流程。重建分为以下阶段: 1) 节点清空目录信息(锁的路由表)以及节点持有的锁,这是因为锁资源信息需要重新路由;2) 对于之前节点持有的锁,按照原来的路由策略和顺序重新发起加锁,这个过程会将整个集群的锁的目录信息重新建立起来,锁的 Master 重新确定。由于每个节点对仅对自身重新加锁,那么对于发生故障被删除节点而言,它之前持有的锁 Master 会被新节点替代;3) 所有节点完成重新加锁流程后,就可以执行正常的加解锁流程了。




从上述过程,我们可以看到集群发生节点成员变更时,恢复过程是非常复杂的。为了减少这种情况的发生,当一个节点通信失败后,会等待一定时间,超过该间隔后仍无法正常通信,才会执行删除节点的流程。一个节点如果仅是发生重启,没有达到需要触发成员变更的阈值,那么只需要恢复这个节点就可以了。在这个过程中,仅仅该节点的锁相关信息丢失了,对于集群的其他节点没有影响。重启过程中,发往该节点的请求将会被 Pending 住,直到该节点恢复。


发生重启的节点,上面大部分锁仍能恢复。节点上的锁由两部分组成,一部分为Local Lock,表示发起加锁的为节点自身。另一部分为 Remote Lock,表示由其他节点发起的加锁。对于Local Lock,其他节点没有信息无法恢复,但不存在竞争,也无需恢复;对于 RemoteLock,可以从其他节点的 shadow 信息中进行恢复。


3.4 些许思考


从成员变更过程,我们可以看到,Connection Manager 在DLM中承担了极其关键的角色,这个也是整个设计中最为复杂的地方,当出现节点故障时,由 Connection Manager 统一协调锁的重新分配,事实上承担了我们所谓的分布式锁管控平面的工作。DLM的优点是什么?负责分布式锁资源分配的数据平面不用考虑整个系统的容错,可以很均衡地让更多机器参与到资源分配,并且锁资源信息不需要落盘,不需要走共识协议做容错,只需要关注抢锁的互斥性和抢锁效率问题,这个抢锁效率,服务水平扩展能力都将非常有优势。


通过上述对 DLM 的加锁,解锁及成员变更过程进行剖析,这个里面还是有比较清晰的管控平面与数据平面的解耦设计,当然,实现过程很复杂,特别是failover这块恢复逻辑。但这种思想还是非常好的,值得我们做架构设计时候借鉴。尤其要提到一点,不同于 DLM 起源的 1980 年代,后期业界有了 Paxos/Raft/EPaxos 等共识协议,我们也有了类似 ZooKeeper/Etcd 等基于共识协议的一致性协议,我们的分布式锁管理器的管控平面完全可以用起来这些成熟的三方组件。


四、最佳实践


阿里云存储部门拥有着从块存储到文件存储,对象存储,日志存储,以及表格存储等全球最完整的存储产品体系。图 10 展示了当前存储产品采用的非常通用的基于分区调度模型的系统架构。整个业务系统按照管控平面与数据平面来划分,其中数据平面将用户的存储空间按照一定规则分割成若干分区,在运行时一个分区会被分配至某个服务器提供服务,一个服务器可以同时加载多个分区。分区不使用本机文件系统存储持久化数据,其拥有的全部数据均会存储在盘古分布式文件系统中的特定目录。基于如此的分区调度模型,当某个服务器发生宕机的时候,它承载的分区需要被重新调度,快速迁移至其它健康的服务器继续提供服务。

image.png

图10 云存储基于盘古+女娲的通用的分区调度设计框架


在云存储的分区调度模型中,有关分区资源的互斥访问(即任何时刻任一个分区必须至多为某一台服务器所加载并提供读写访问服务)是存储系统提供数据一致性的基石,必须得到保障。事实上,云存储的最佳实践中有着类似 DLM 的设计哲学,将分布式锁管理器的容错问题抽离出来,借助女娲-飞天分布式协同基础服务提供的选主功能来实现,进而可以专注在分布式锁资源的调度策略:


1)管控调度器负责具体分区资源的互斥分配,这里结合不同存储业务的特殊需要,可以演进出不同的调度策略,从分布式锁的均衡性,分布式锁的抢锁效率,分布式锁的切换精度等不同维度做专项优化;


2)分布式锁管理器中最复杂的容错能力,通过依赖女娲选主功能实现,并且通过女娲的服务发现能力实现管控节点平滑上下线;


3)存储系统的数据最终均存放在盘古-飞天分布式存储文件系统,从具体的分区数据,到管控调度的元数据,这些信息都会放入盘古。盘古提供了高可靠高性能的存储服务以及 Fencing 保护能力,保障数据一致性;

image.png

图11 基于盘古分布式文件系统提供的 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.html2.Distributed Lock Manager -- Apache Helix:https://helix.apache.org/1.0.2-docs/recipes/lock_manager.html3.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_Manager5.Cache Fusion: Extending Shared-Disk Clusters with Shared Caches -- Oracle RAC:http://www.dia.uniroma3.it/~vldbproc/086_683.pdf


作者 | 安凯歌(云秋)

来源 | 阿里云开发者公众号

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
消息中间件 存储 中间件
常用本地事务和分布式事务解决方案模型 2
常用本地事务和分布式事务解决方案模型
252 1
|
算法 关系型数据库 API
常用本地事务和分布式事务解决方案模型 1
常用本地事务和分布式事务解决方案模型
320 1
|
5月前
|
机器学习/深度学习 算法 PyTorch
深度学习分布式模型
深度学习分布式模型
|
4月前
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
3天前
|
存储 分布式计算 负载均衡
分布式计算模型和集群计算模型的区别
【10月更文挑战第18天】分布式计算模型和集群计算模型各有特点和优势,在实际应用中需要根据具体的需求和条件选择合适的计算架构模式,以达到最佳的计算效果和性能。
16 2
|
13天前
|
存储 分布式计算 负载均衡
EMQ
|
4月前
|
传感器 人工智能 安全
EMQX 与 MQTT: AI 大模型时代的分布式数据中枢
在以数据为核心的 AI 时代,基于 MQTT 协议的消息服务器 EMQX 能帮助企业更好的利用人工智能和机器学习模型,是智能化系统中核心的数据基础软件。
EMQ
215 16
|
3月前
|
存储 NoSQL MongoDB
(四)成为分布式高手必经之路:理解那些工作在分布式系统底层的一致性模型
在分布式领域里,一致性成为了炙手可热的名词,缓存、数据库、消息中间件、文件系统、业务系统……,各类分布式场景中都有它的身影,因此,想要更好的理解分布式系统,必须要理解“一致性”这个概念。本文就展开聊聊 分布式系统里的一致性模型。
|
3月前
|
算法 异构计算
自研分布式训练框架EPL问题之帮助加速Bert Large模型的训练如何解决
自研分布式训练框架EPL问题之帮助加速Bert Large模型的训练如何解决
|
4月前
|
人工智能 PyTorch TensorFlow
分布式训练:大规模AI模型的实践与挑战
【7月更文第29天】随着人工智能的发展,深度学习模型变得越来越复杂,数据集也越来越大。为了应对这种规模的增长,分布式训练成为了训练大规模AI模型的关键技术。本文将介绍分布式训练的基本概念、常用框架(如TensorFlow和PyTorch)、最佳实践以及可能遇到的性能瓶颈和解决方案。
676 2