Raft算法之成员变更

成员变更是跟leader选举、日志同步、安全、日志压缩一样,都是Raft算法的核心概念。但成员变更是最难理解的。所以单列一篇总结。

将成员变更纳入到算法中是Raft易于应用到实践中的关键,相对于Paxos,它给出了明确的变更过程(实践的基础,任何现实的系统中都会遇到因为硬件故障等原因引起的节点变更的操作)。

显然,我们可以通过shutdown集群,然后变更配置后重启集群的方式达到成员变更的目的。但是这种操作会损失系统的可用性,同时会带来操作失误引起的风险。支持自动化配置,即配置可以在集群运行期间进行动态的变更(不影响可用性)显然是一个非常重要的特性。

常见问题

一、常规处理成员变更有什么问题?

会出现双Leader问题。

成员变更是在集群运行过程中副本发生变化,如增加/减少副本数、节点替换等。

成员变更也是一个分布式一致性问题,既所有服务器对新成员达成一致。但是成员变更又有其特殊性,因为在成员变更的一致性达成的过程中,参与投票的进程会发生变化。

如果将成员变更当成一般的一致性问题,直接向Leader发送成员变更请求,Leader复制成员变更日志,达成多数派之后提交,各服务器提交成员变更日志后从旧成员配置(Cold)切换到新成员配置(Cnew)。

因为各个服务器提交成员变更日志的时刻可能不同,造成各个服务器从旧成员配置(Cold)切换到新成员配置(Cnew)的时刻不同。

成员变更不能影响服务的可用性,但是成员变更过程的某一时刻,可能出现在Cold和Cnew中同时存在两个不相交的多数派,进而可能选出两个Leader,形成不同的决议,破坏安全性。

成员变更的某一时刻Cold和Cnew中同时存在两个不相交的多数派

上图中Server1可以通过自身和Server2的选票成为Leader(满足旧配置下收到大多数选票的原则);Server3可以通过自身和Server4、Server5的选票成为Leader(满足新配置下,即集群有5个节点的情况下的收到大多数选票的原则);此时整个集群可能在同一任期中出现了两个Leader,这和协议是违背的。

二、有什么解决方案?

两种方案,

  • 方案一、一阶段成员变更;
  • 方案二、二阶段成员变更;

方案一、一阶段成员变更

如果增强成员变更的限制,假设Cold与Cnew任意的多数派交集不为空,这两个成员配置就无法各自形成多数派,那么成员变更方案就可能简化为一阶段。

那么如何限制Cold与Cnew,使之任意的多数派交集不为空呢?方法就是每次成员变更只允许增加或删除一个成员。

可从数学上严格证明,只要每次只允许增加或删除一个成员,Cold与Cnew不可能形成两个不相交的多数派。

一阶段成员变更:

  • 成员变更限制每次只能增加或删除一个成员(如果要变更多个成员,连续变更多次)。
  • 成员变更由Leader发起,Cnew得到多数派确认后,返回客户端成员变更成功。

一次成员变更成功前不允许开始下一次成员变更,因此新任Leader在开始提供服务前要将自己本地保存的最新成员配置重新投票形成多数派确认。

Leader只要开始同步新成员配置,即可开始使用新的成员配置进行日志同步。

方案二、二阶段成员变更

为了保证安全性,Raft采用了一种两阶段的方式。

集群先从旧成员配置Cold切换到一个称为多边共识的中间阶段(joint consensus)joint consensus 是旧成员配置Cold和新成员配置Cnew的组合 Cold U Cnew,也即Cold+new,一旦 joint consensus Cold+new被提交,系统再切换到新成员配置Cnew

joint consensus 状态下:

  • 日志被提交给新旧配置下所有的节点;
  • 新旧配置中所有机器都可能成为Leader;
  • 达成一致(选举和提交)要在两种配置上获得超过半数的支持;

Raft两阶段成员变更过程如下:

Leader收到从Cold切换成Cnew的成员变更请求;Leader做了两步走的操作:

第一阶段

Leader在本地生成一个新的 log entry,其内容是Cold+new,代表当前时刻新旧成员配置共存,写入本地日志,同时将该 log entry 复制至Cold+new中的所有副本。在此之后新的日志同步需要保证得到Cold和Cnew两个多数派的确认

Follower收到Cold+newlog entry 后更新本地日志,并且此时就以该配置作为自己的成员配置;

如果Cold和Cnew中的两个多数派确认了Cold+new这条日志,Leader就提交这条 log entry

配置变更唯一的不同在于它们会立即生效,一旦服务器将新配置记录到日志中,那么它就立刻生效,并不需要等待该日志记录变为已提交状态。所以此时在领导者上已经认为 Cold+new 已生效,这意味着对于要提交的任何日志条目,要求该条目分别在新旧配置服务器下同时都成为大多数。

第二阶段

接下来Leader生成一条新的 log entry,其内容是新成员配置Cnew,同样将该 log entry 写入本地日志,同时复制到Follower上;

Follower收到新成员配置Cnew后,将其写入日志,并且从此刻起,就以该配置作为自己的成员配置,并且如果发现自己不在Cnew这个成员配置中会自动退出;

Leader收到Cnew的多数派确认后,表示成员变更成功,后续的日志只要得到Cnew多数派确认即可。Leader给客户端回复成员变更执行成功。

-w585

Review成员变更

一、如果当前的Leader不在Cnew的配置中会怎么样(即当前的Leader是一个要被下线的节点)?

在Cold+new的状态下,Leader依旧可用;在Cnew被提交之后Leader实际已经从集群中脱离,此时可以对Leader节点进行下线操作,而新集群则会在Cnew的配置下重新选举出一个Leader。

二、如果在配置分发过程中Leader Crash了会怎么样?

这个问题要分为多种情况:

  1. Cnew已经分发到超过半数节点;
  2. Cnew还没分发到超过半数的节点;

情况1:Cnew已经分发到超过半数节点

集群开始重新选举,此时在Cnew的规则下,旧节点(不存在新配置中的节点)不会赢得选举(因为他们要在Cold+new的情况下决定,但是拿不到Cnew的选票),只有拿到Cnew的节点可能成为Leader并继续下发Cnew配置,流程恢复。

情况2:Cnew还没分发到超过半数的节点

这种情况下,Cold+new和Cnew的节点都可以成为Leader,但是无所谓,因为无论谁成为Leader,都能根据当前的配置继续完成后续流程(如果是Cnew那么相当与完成了最终的配置,不在Cnew的节点会因为没有心跳数据而失效)

三、旧节点下线造成的问题:旧节点收不到心跳触发选举,发送请求给Cold+new中的节点,是否会影响集群正常运行

Raft的处理方式:当节点确信有Leader存在时,不会进行投票(在Leader超时之前收到新的投票请求时不会提升term和投票)。且开始选举之前等待一个选举超时时间,这样在新Leader正常工作的情况下,不会受到旧节点的影响。

四、新的服务器没有任何数据,加入进来怎么保证系统的可用性(这个时候新日志没办法提交就没办法响应给客户端)?

新加入的节点需要时间复制数据,在这个过程完成之前,Raft采用以下机制来保证可用性:新加入节点没有投票权(Leader复制日志给他们,但是不将他们考虑在机器数量里面——即在判断是否超过半数时不把这些节点考虑在内),直到这些节点的日志追上其他节点。

结束

两阶段成员变更比较通用且容易理解,但是实现比较复杂,同时两阶段的变更协议也会在一定程度上影响变更过程中的服务可用性,因此我们期望增强成员变更的限制,以简化操作流程。

两阶段成员变更,之所以分为两个阶段,是因为对Cold与Cnew的关系没有做任何假设,为了避免Cold和Cnew各自形成不相交的多数派选出两个Leader,才引入了两阶段方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章