当前位置: 首页 > 后端技术 > Java

一步实现JointConsensus的两阶段成员变更

时间:2023-04-01 19:16:53 Java

简介:Raft提出的两阶段成员变更JointConsensus是业界主流的成员变更方式,极大地促进了成员变更的工程化应用。但是JointConsensus的成员分两阶段变更,一次变更需要提交两份日志,在某些系统中直接使用不方便。那么联合共识的成员变更是否可以一步到位呢?作者|祥光源|阿里技术公众号1简介分布式系统运行过程中节点经常失效,需要支持节点的动态增删改查。成员变更是分布式系统中无法回避的话题,尤其是在一致性系统中,对于提升运维能力和服务可用性有很大的帮助。Raft提出的两阶段成员变更JointConsensus是业界主流的成员变更方式,极大地促进了成员变更的工程化应用。但是JointConsensus的成员分两阶段变更,一次变更需要提交两份日志,在某些系统中直接使用不方便。虽然Raft也提出了单步修改成员的方法,但是单步修改成员的方法一次只能增加或减少一个成员,限制比较大,容易踩到陷阱,所以一般不推荐使用。那么很自然地会想到,联合共识的成员变更是否可以一步到位呢?本文深入探讨了这个问题。2.成员变更我们先回顾一下共识协议中的成员变更问题。成员变更是在集群运行过程中改变运行共识协议的节点,如增加、减少节点、更换节点等,成员变更过程不得影响系统的可用性。成员变更也是一个一致性问题,即所有节点都同意成员配置。但是,成员变更有其特殊性,因为在成员变更过程中,参与投票的成员会发生变化。图1.在成员变更的某个时刻,Cold和Cnew中有两个不相交的多数。如果将成员变更看做一般一致性问题,在成员变更过程中,各节点从旧成员配置Cold切换到新成员配置Cnew的时刻可能存在差异,并且可能存在两个不相交的多数Cold和Cnew在某个时刻,形成双倍quorum,打破了一致性。为了解决这个问题,Raft提出了一种两阶段的成员变更方法JointConsensus。1ChangeofJointConsensusMembers改变JointConsensusMembers为了避免doublequorumproblem,引入联合成员配置Cold,new作为过渡配置。Cold,new是Cold和Cnew的组合。QuorumofCold和Cold,new相交,QuorumofCold,new和Cnew也相交。Membership变化先从Cold切换到Cold,new,提交Cold,new后再切换到Cnew,保证Cold和Cnew不会同时使用,不会形成双重Quorum,保证安全。图2Cold与Cold、new与Cnew联合共识的Quorum集之间的关系使用两个日志来完成成员变更过程。Leader收到成员变更请求后,首先同步一个Cold,new日志给Cold和Cnew,然后所有的日志都需要经过大多数Cold和Cnew的确认。只有在Cold和Cnew都达到多数后,才能提交冷新日志。之后,Leader再同步一个只包含Cnew的日志给Cold和Cnew。之后,日志只需要经过大多数Cnew的确认。只有在Cnew中达到多数后,才能提交Cnew日志。此时成员变更完成,不在Cnew的成员会自动下线。图3JointConsensus的成员变更过程如果在成员变更过程中发生Failover,旧的Leader宕机,Cold和New中的任何一个节点都可能成为新的Leader。如果新Leader上没有Cold和New日志,继续使用Cold和Follower,如果有冷和新日志,会被新leader截断,如果新leader返回cold,membershipchange会失败.2单步成员变更JointConsensus的成员变更之所以需要两个阶段,是因为没有对Cold和Cnew之间的关系做任何假设。阶段计划。如果加强对成员变更的限制,假设Cold和Cnew的Quorum交集不为空,Cold和Cnew不能形成双重Quorum,成员变更可以简化为一个阶段。实现单步成员变更的关键是限制Cold和Cnew,使Cold和Cnew的Quorum交集不为空。那么如何限制Cold和Cnew使得Quorum交集Cold和Cnew不为空呢?方法是每次改变一个成员只允许增加或删除一个成员。图4.添加或删除成员时,QuorumofCold和Cnew会添加或删除成员。如图4所示,从数学上可以严格证明,只要每次只允许增加或删除一个成员,Cold和Cnew是不可能形成两个不相交的Quorum的。因此,只要一次只增加或删除一个成员,就可以直接从Cold切换到Cnew,无需转换成员配置,实现单步成员变更。单步成员更改一次只能更改一个成员。如果需要更改多个成员,例如更换成员,可以通过执行多个单步成员更改来实现。单步换成员的理论虽然简单,但是却埋下了很多陷阱,实际使用起来也不是那么简单。这个在之前的文章RaftMembershipChanges的工程实践中有详细介绍。三阶段或两阶段一步实现成员变更联合共识成员变更很常见,但使用两个阶段,一次变更需要提交两份日志。虽然单步修改成员只需要提交一份日志,但是限制比较大,一次只能修改一个成员。.两者的优点可以结合吗?JointConsensus的成员是否可以一步更改?在JointConsensus的成员变更过程中,Cold和New日志的提交已经让所有节点都同意了Cnew的配置,那么Cnew日志的作用是什么呢?Coldnewlog提交后是否可以从Coldnewconfiguration切换到Cnewconfiguration?这样一来,是否可以在没有Cnew日志的情况下一步实现呢?考虑Cnew日志在联合共识成员变更中的作用。Cnew日志在Cold,new日志提交后发起提案。节点收到并持久化Cnew日志后,从Cold,new配置切换到Cnew配置。不在Cnew配置的成员在Cnew提交日志后下线。根据这个过程,可以总结出Cnew日志的作用:通知节点在收到并持久化Cnew日志后,从Coldnew配置切换到Cnew配置。Cnew日志提交后,通知不在Cnew配置中的节点下线。成员变更过程中发生Failover后,有本地Cnew日志的节点优先投票。如果能在不同时使用Cnewlog的情况下完成Cnewlog的工作,是否可以一步实现两阶段的JointConsensus成员变更?事实上,这条路径已经被系统地探索过了。1ZooKeeper成员变化ZooKeeper从3.5.0版本开始支持在Zab的基础上进行成员变化。ZooKeeper具有PrimaryOrder特性,使用两次日志进行JointConsensus成员变更并不能保证PrimaryOrder特性。ZooKeeper为了满足成员变化的通用性又不失PrimaryOrder的特性,在论文中提出了自己的member《Dynamic Reconfiguration of Primary/Backup Clusters》改变方法,并在ZooKeeper中应用了这种方法,比Raft的提议更早。图5是ZooKeeper成员更改协议。图中旧成员配置用S表示,新成员配置用S'表示,P为Leader节点。图5为B3、B4节点替换B1、B2节点的过程:5ZooKeeper成员变更协议初始化:为了让新节点赶上最新的数据,新成员在S中配置新节点B3、B4'首先连接到当前的主节点P,P将其当前状态作为初始状态传递给它们。在Zab协议中,当备节点连接到主节点时,会自动发生这种状态转移,并会继续接收来自主节点P的所有后续操作日志(如图中的Op1和Op2)。在这个过程中,节点B3、B4不参与投票。步骤1:主节点P将成员变更日志COP发送给与其相连的所有备用节点(SUS')。COP日志携带旧成员配置S和新成员配置S',等待旧成员配置S中的节点确认。一旦S中的多数确认COP日志,则对S'达成共识。Step2:COP日志之前的日志只需要旧成员配置S中的多数确认,旧成员配置和新成员配置(SUS')都可以提交;在COP命令之后,S'的激活消息ACTIVATE之前,日志需要经过新老成员配置(SUS')的两次多数确认,只能在S'中提交;S'的激活消息ACTIVATE后的日志只需要在S'中确认并提交即可。步骤3:主节点P等待COP日志和S'中COP之前的日志的确认。第四步:一旦新老成员配置(SUS1)都确认了COP日志,主节点P提交COP日志并广播激活消息ACTIVATE激活新成员配置S',完成成员变更。与日志同步消息类似,ACTIVATE消息中包含主节点P的Epoch,Epoch过时的ACTIVATE消息将被忽略。如果在成员变更过程中发生Failover,可能会出现以下情况:如果在发送COP日志之前发生Failover,则成员变更失败,重新选举master后旧成员配置继续工作;如果发送COP日志并且在ACTIVATE故障转移之前,新旧成员配置中的任何节点都可能成为新的领导者。如果新的leader上没有COP日志,则成员变更失败;如果新的leader上有COP日志,继续完成未完成的成员变更流程。如果Failover发生在ACTIVATE之后,则成员更改已经完成,但不能保证新的领导者必须在新的成员配置中。此时,不在新成员配置中的节点不能下线。所以发送ACTIVATE消息后,需要在新成员配置中提交no-oplog。提交no-op日志后,可以保证新leader一定在新member配置中,不在新member配置中的节点可以安全下线。ZooKeeper使用异步的Commit消息,即ACTIVATE消息,通知节点从新旧成员配置切换到新成员配置。使用异步无操作日志记录来安全地关闭不在新成员配置中的节点。ZooKeeper的ACTIVATE消息和异步??no-op日志在JointConsensus成员变更中扮演Cnew日志的角色。2.改进了一步实现。ZooKeeper成员变更协议不像JointConsensus成员变更那样简单。JointConsensus成员变更可以通过两个阶段使用协议本身,没有太多限制,以确保成员变更的安全性。那么ZooKeeper成员变更协议是否可以改进呢?ZooKeeper成员变更协议中的异步ACTIVATE消息和no-oplog实际上是为了完成Cnew日志在JointConsensus成员变更中的作用。了解了这些之后,也可以将JointConsensus成员变更的Cnew日志改成异步的。冷,新日志提交后,认为成员变更完成,然后异步提交Cnew日志。Cnew日志之所以可以改为异步,是因为在Cold新日志提交后才认为成员变更完成,因为一旦Cold新日志提交,所有节点就新的配置达成了共识member,并且不会有回滚当配置oldmember时,剩下的过程最终会完成,最终会提交Cnew日志。另一种改进方式是继续保留ACTIVATE消息,但不使用no-oplog,那么如何保证节点切换到新的member配置有优先选举权呢?根据选举的安全性,日志最新的节点优先投票,因此在选举时可以携带该节点当前的成员配置,如果日志是最新的,则优先投票对于已切换到新成员配置的节点。保证切换到新成员配置的节点有优先选举权。在新成员配置中的大部分节点切换到新成员配置后,不在新成员配置中的节点可以安全下线。4.总结JointConsensus成员变更的提出极大地促进了成员变更的工程化应用。简单、美观、通用,但是采用了两个阶段,一次变更需要提交两份日志。本文讨论了两阶段联合共识成员变更的单步实现方法,并进行了一些改进,为成员变更的工程应用提供了更多的选择。原文链接本文为阿里云原创内容,未经许可不得转载。