1.什么是分布式锁?什么是分布式锁?对于这个问题,相信很多同学既熟悉又陌生。随着分布式系统的快速发展和广泛应用,共享资源的互斥访问成为很多业务必须面对的需求。在这种场景下,人们通常会引入分布式锁来解决问题。我们通常使用什么样的分布式锁服务?有开源的MySQL、Redis、ZooKeeper、Etcd等三方组件可供选择,当然也有集团开发的Tair、Nuwa等分布式锁服务商。总的来说,我们对分布式锁的需求大致可以分为以下两类应用场景:实现操作原子性:在单机环境下,为了实现多进程或多线程对共享资源操作的原子性,我们可以使用内核提供的SpinLock或者Mutex机制来保证只有一个进程或者线程操作共享资源。类似于单机环境中对锁的需求,在分布式环境中,我们通常使用分布式锁来控制多台机器上节点的并发操作,避免数据或状态的损坏。实现系统高可用:为了服务的高可用,往往需要部署多个节点来实现服务冗余,避免单点故障导致的服务不可用。借助分布式锁+服务发现实现的选主功能,节点根据抢锁成功与否决定是否成为主节点对外提供服务。当一个节点宕机时,其他备份节点可以通过竞争分布式锁的所有权继续提供访问服务。分布式锁的业务需求和场景看似比较简单,但实际上在使用分布式的过程中锁,我们总会提出这样那样的新需求,分布式锁场景似乎没有统一的解决方案。那么,分布式锁内部是如何实现的呢?或者应该如何实现?这就是我们本文要讨论的内容,也希望我们的讨论能够让读者朋友们对分布式锁的原理有一定的了解,在技术选型时提供更多的指导。2.设计模型我们应该为分布式锁建立什么样的设计模型?这个问题可以换个角度来看。我们应该建立什么样的合理属性来创建分布式锁模型?我们不妨参考一下业界的两个定义。首先是ApacheHelix(开源社区流行的通用集群资源管理框架,可用于自动管理存在于集群节点上的分区,具有副本的分布式资源)对于分布式锁管理器性质的定义:a)均匀分布,不是最先获取所有分布式锁的节点;b)平衡重调度,需要妥善处理持有分布式锁的节点意外退出后的锁资源分配问题;c)Rebalance,当有新的节点加入时,节点之间的锁资源应该重新分配以达到平衡。可见Helix对分布式锁模型的定义非常强调平衡。考虑到它负责集群中分区资源的调度,这种关注也就不足为奇了。图1Helix提出的分布式锁管理器的本质让我们看看另一个著名的Redis对分布式锁本质的定义,它提出分布式锁模型必须遵循的三个原则:a)绝对互斥,同时只有一个客户端可以持有分布式锁;b)finallyavailable,如果持有分布式锁的客户端意外退出,则可以重新分配相关的分布式锁资源;c)服务容错,提供分布式锁的服务必须具备容错能力,即使部分节点崩溃,也不会影响整体的分布式锁服务。图2Redis提出的分布式锁管理器的本质结合我们自己的经验,我们非常认同Redis对分布式锁模型的基本约束。这些实际上是实现分布式锁服务必须考虑的几个属性。而且,Redis相关的文章继续讨论分布式锁的其他特性约束。其实如下图3所示,我们从三个维度总结了分布式锁模型实现需要考虑的属性。第一个维度是最基本的约束,和Redis提出的完全一样。我们称之为:互斥、容错、最终可用性;第二层提出的分布式锁管理器需要关注一些锁的特性,比如锁的抓取效率、分布式锁的平衡、锁的切换精度、锁的可重入性等。除此之外,还有一个和数据相关的约束分布式锁实现时必须考虑的一致性和正确性保证,即对时钟漂移影响的保护和响应。图3分布式锁设计中需要考虑的三个维度的性质model关于分布式锁管理器实际实现中需要考虑的数据一致性和正确性的话题,其中一个话题就是不可靠的walltime,这个可以通过引入non-walltimeMonoticTime来解决,而这个文章不会进一步讨论这个问题。另外一个话题,在实际使用分布式锁服务访问共享资源时,必须借助Fencing能力,实现资源访问的绝对互斥。大神MartinKleppmann提供了一个很好的案例说明,如下图4所示,Client1首先获取了分布式锁的所有权,在操作数据时发生了GC,在长期的“Stop-The-World”GC中在此过程中,锁的所有权丢失,Client2竞争锁的所有权,开始操作数据。这样一来,Client1的GC完成后,Client1和Client2会同时操作数据,导致数据不一致。图4分布式锁缺少Fencing保护可能会导致数据不一致。针对以上问题,解决方案是引入共享资源访问的IOFence能力。如下图5所示,全局锁服务提供全局自增Token,Client1拿锁返回的Token为33,带入存储系统,发生GC。当Client2成功抢到锁返回Token34,带入存储系统,存储系统会拒绝后续请求小于34的Token,GC会在很长一段时间后恢复稍后,当Client1再次写入数据时,因为底层存储系统记录的Token已经更新,携带Token值为33的请求会被直接拒绝,从而达到数据保护的效果。图5基于Fencing的数据一致性保证回到主旨文章,如何实现一个高效的分布式锁管理器?首先抛出一个观点,分布式锁管理器也可以按照控制平面和数据平面来划分。图3中提到的分布式锁的性质可以分为不同的平面分别负责。我们的观点不是第一个。事实上,在OSDI'20的最佳论文-《Virtual Consensus in Delos》中,Facebook的研究团队对共识协议的设计进行了深入的讨论,非常精彩。文章提到像Raft这样的分布式共识协议,它也可以拆分为控制平面和数据平面。前者负责容错、成员变更、角色调整,后者负责排序和持久化。通过将两个平面解耦,共识协议立即变得非常灵活。图6:VirtualConsensus对Delos数据平面控制的看法我们分布式锁模型的实现是否也可以参考类似的思路?负责容错和成员变更的逻辑被转移到控制平面,而数据平面负责分布式锁的其他功能,如互斥、最终可用性、锁抢效率。答案是肯定的,好吧,即使这个想法也不是我们的第一个。在数据库领域,一直有这样的流派来演化这种分布式锁系统。它们统称为DLM(DistributedLockManager)。典型的有OracleRAC、GFS2、OCFS2、GPFS,接下来说说DLM。3.什么是DLM?DLM的思想来源于《The VAX/VMS Distributed Lock Manager》,于1984年首次应用于VAX/VMSV4.0。接下来我们以OracleRAC为例来说明DLM的设计思想。OracleRAC运行在集群之上,基于内存融合技术,使得Oracle数据库具备高可用性和极致性能。如果集群中的一个节点发生故障,Oracle可以继续在其余节点上运行。为了保证多个节点写入内存Page的过程的一致性,使用分布式锁管理器(DLM)来处理分布式锁资源的分配和释放。如图7所示,DLM是去中心化设计,集群中所有节点都是平等的,每个节点都维护着一部分锁信息。那么在申请锁的时候,谁来决定锁的分配呢?在DLM中,每把锁都有一个Master的概念,由Master协调授权决定是否允许加锁或解锁。每个节点都可能成为LockMaster。当每个节点管理这些锁资源时,这些锁资源被组织成树状结构。通过树节点的父子继承关系,可以优化锁的粒度,提高加解锁的效率。图7DLM的分布式锁角色关系在加锁或者解锁的过程中,有以下几种涉及的节点:a)请求者:发起锁定或解锁的节点;b)DirectoryNode:锁的目录节点,存储锁的主人被哪个节点持有锁这一类信息;d)Master:锁的持有者,实际管理者,负责锁的分配和释放。下面我们通过具体的例子来描述分布式锁的分配和释放的具体过程。示例中有A、B、C三个节点,其中A为Requester,B为DirectoryNode,C为Master节点。3.1加锁过程图8是在其他节点加锁的过程,是所有加锁情况中最耗时的,最多需要2轮交互。本地建立资源后,在本地锁定有继承关系的资源,不与其他节点交互即可:1.节点A锁定资源R1,首先在本地构造锁对象,也称为锁的影子,但此时节点A还没有成功锁定;2、节点A通过hash计算资源R1为节点B,计算出R1对应的目录管理器;3.节点A请求节点B,节点B的记录4.节点A向节点C发起对R1的锁请求;5.节点C维护R1的锁请求队列。如果允许A加锁,则返回成功;6.A更新LocalR1锁影子相关信息,锁完成。图8DLM的加锁流程3.2解锁流程图9解锁流程,比较直观,如下:1.节点A解锁资源R1,删除锁本地构建的对象;2.节点A请求节点C请求释放A的锁;3、如果A是队列中的最后一个请求者,节点C会向B发送请求,将R1从目录中移除,这样其他节点就可以成为锁的主人;否则,节点C只需从R1的锁定队列中删除A。图9DLM的解锁流程3.3成员变更上面的加锁和解锁流程只是一个普通的添加和解锁流程。那么,当集群中某个节点出现故障,或者集群中有节点增删时,如何控制分布式锁正常路由分配呢?在DLM中,有连接管理器的角色。除了负责各个节点的网络通信外,另一个重要的功能是当集群节点增加或删除时,节点首先选举一个领导节点进行协调,每个节点都可能成为领导节点。添加或删除节点时会发生以下过??程:重建节点拓扑:leader节点通过两阶段投票向集群中的其他节点发起通知,告知当前集群节点的拓扑,其他节点有权接受或拒绝信息。如果拓扑图没有达成一致(其他节点拒绝拓扑信息),leader会休眠一段时间,其他节点进行leader选举,新的leader会通知其他节点。这样集群中的所有节点就可以对全局拓扑图和锁资源的路由算法达成共识。在成员变更期间,仍然可以发起抢锁请求,但是这些请求会在请求队列中,无法抢锁成功。成员更改后,这些请求将按照它们发起的顺序重新发出。重建节点锁信息:leader会通知其他节点重建锁信息。重建过程分为多个阶段。当所有节点完成一个阶段后,领导者会通知集群中的所有节点进入下一阶段。在重构过程中,如果任何一个节点发生故障,需要重新发起选举和重构过程。重构分为以下几个阶段:1)节点清除目录信息(锁路由表)和节点持有的锁,因为锁资源信息需要重新路由;2)对于之前节点持有的锁,按照原来的路由策略和顺序重新发起加锁,这个过程会重新建立整个集群的锁目录信息,重新确定锁的主人。由于每个节点对只对自己重新加锁,那么对于因故障被删除的节点,其之前持有的锁Master将被新节点取代;3)所有节点完成重新加锁流程后,即可执行正常的加解锁流程。从上面的流程可以看出,当集群节点成员关系发生变化时,恢复过程非常复杂。为了减少这种情况的发生,当一个节点无法通信时,它会等待一定的时间,等到间隔后无法正常通信时,才会执行删除节点的过程。如果一个节点只是重启,没有达到需要触发成员变化的阈值,那么只需要恢复这个节点。在这个过程中,只有该节点的锁相关信息丢失,对集群其他节点没有影响。在重启过程中,发送给节点的请求会一直处于Pending状态,直到节点恢复。重启节点上的大部分锁仍然可以恢复。节点上的锁由两部分组成,一部分是LocalLock,也就是节点自己发起锁。另一部分是RemoteLock,即由其他节点发起的锁定。对于LocalLock,其他节点没有信息,无法恢复,但不存在竞争,不需要恢复;对于RemoteLock,可以从其他节点的影子信息中进行恢复。3.4一些思考从成员变更过程中,我们可以看出ConnectionManager在DLM中起着极其关键的作用,这也是整个设计中最复杂的部分。当一个节点出现故障时,连接管理器协调锁的重新分配,实际上承担了我们所说的分布式锁控制平面的工作。DLM的优点是什么?负责分布式锁资源分配的数据平面不需要考虑整个系统的容错性。可以让更多的机器均衡参与资源分配,锁资源信息不需要放在磁盘上,也不需要遵循容错的共识协议。它只需要专注于抓取锁的互斥和抓取锁的效率,抓取锁的效率和扩展服务级别的能力将是非常有优势的。通过以上对DLM加锁、解锁和成员变更流程的分析,控制面和数据面的解耦设计还是比较清晰的。当然,实现过程非常复杂,尤其是failover的恢复逻辑。但是这种思路还是很好的,值得我们在做架构设计的时候借鉴。特别需要说明的是,与DLM发端的1980年代不同,后来业界有了Paxos/Raft/EPaxos等共识协议,我们也有了基于ZooKeeper/Etcd等共识协议的共识协议。我们的分布式锁管理器这些成熟的三方组件完全可以在管控面使用。4.最佳实践阿里云存储部门拥有全球最完善的存储产品体系,从块存储到文件存储、对象存储、日志存储、表存储。图10是基于当前存储产品采用的分区调度模型的一个非常通用的系统架构。整个业务系统按照控制平面和数据平面进行划分。数据平面将用户的存储空间按照一定的规则划分为若干个分区。在运行过程中,一个分区会分配给某个服务器提供服务,一台服务器可以同时加载多个分区。分区不使用本地文件系统来存储持久化数据,它所拥有的所有数据都会存储在盘古分布式文件系统中的特定目录下。基于这样的分区调度模型,当一台服务器宕机时,其承载的分区需要重新调度并快速迁移到其他健康的服务器上继续提供服务。图10基于盘古的云存储通用分区调度设计框架+Nuwa在云存储的分区调度模型中,分区资源的互斥访问(即任何分区在任何时候都必须至多为一台服务器加载并提供读写访问服务)是存储系统实现的基石提供数据的一致性,必须得到保证。事实上,云存储的最佳实践有一个类似DLM的设计理念,将分布式锁管理器的容错问题抽取出来,借助Nuwa-飞天分布式协同基础服务提供的选主功能实现,以及那么可以重点关注分布式锁资源的调度策略:1)管控调度器负责具体分区资源的互斥分配。这里,结合不同存储服务的特殊需求,可以演化出不同的调度策略,从分布式锁的均衡、分布2)分布式锁管理器中最复杂的容错能力是依靠女娲的主选函数实现的,并通过女娲的服务发现能力,实现控制节点上下线的流畅管理;3)存储系统的数据最终存储在盘古-飞天分布式存储文件系统中。从具体的分区数据到用于管理、控制和调度的元数据,这些信息都会被放入盘古。盘古提供高可靠、高性能的存储服务和Fencing保护能力,保证数据的一致性;图11基于盘古分布式文件系统的Fencing保护分布式锁提供Fencing保护的核心点是带GotoToken校验。盘古作为统一的存储基础,通过引入特殊的InlineFile文件类型,配合SealFile操作,实现了类似IOFence的防护能力:lock继续写数据;b)为每个分区引入InlineFile,为盘古文件的元数据操作关联InlineFile相关的CAS判断,从而防止分布式锁的老主人打开新文件。如图11所示,这两个功能的结合实际上为存储系统中写入数据提供了token校验支持。我们看到,在云存储的DLM实现中,有一个通用的基于分区的调度器,女娲提供容错保证,盘古提供资源围栏保护。这是云存储的最佳实践。五、总结分布式锁提供了在分布式环境下对共享资源的互斥访问,在分布式系统中得到了广泛的应用。本文从分布式锁的本质出发,探讨分布式锁的模型设计。关于分布式锁系统,我们讨论了控制面和数据面解耦的架构设计,介绍了分布式锁在阿里云存储场景下的最佳实践。希望我们的分享对读者朋友们有所帮助。
