vlambda博客
学习文章列表

Raft算法之集群成员变化篇


一、集群成员变化可能带来的问题

集群成员变化是一个常见操作,主要是增加、删除节点,主要的场景有升级、服务器老化等,当然如果我们对服务的SLA没太大要求,直接关闭集群是最简单的办法。但如果要保证系统的可用性而动态地添加、删除节点并且保证不会脑裂等问题则需要一个安全的算法,所以Raft算法把这一部分也纳入其中。


直接将集群成员配置从旧配置切到新配置会有脑裂问题,举个例子:


上图中集群原来有3个节点:Server1、Server2、Server3,现在要增加2个节点:Server4、Server5,我们来设想下具体的操作步骤:


1、Server4和Server5是新节点,所以这2台机器启动的时候认为集群中有5个节点;

2、然后修改Server3的配置,改成5节点;


这时发生选举,即上图中红色箭头所指的位置,这时候每个节点看到的集群成员如下,为了方便描述,统一将Server字样去掉,即只保留1、2、3、4、5,1表示Server1:

1:1、2、3

2:1、2、3

3:1、2、3、4、5

4:1、2、3、4、5

5:1、2、3、4、5


这时1通过2的投票可以被选举为Leader,因为1和2认为集群只有1、2、3总共3个节点;

3通过3,4,5这3个节点的投票被选举为Leader;


这样集群同时存在1和3两个Leader了。


二、变更方案

官方给的方案是二阶段变更:

集群先从旧成员配置Cold切换到一个过渡成员配置,称为共同一致(joint consensus),共同一致是旧成员配置Cold和新成员配置Cnew的组合Cold U Cnew,一旦共同一致Cold U Cnew被提交,系统再切换到新成员配置Cnew。


具体过程如下:

Leader收到从Cold切成Cnew的成员变更请求,Leader分两步操作:


1、提交配置Cold U Cnew

Leader在本地生成一个新的日志,这个日志的类型是成员配置,其内容是Cold∪Cnew,代表当前时刻新旧成员配置共存,写入本地日志,但并不提交;


Leader同时将该日志复制至Cold∪Cnew中的所有副本,在此之后新的日志同步需要保证得到Cold和Cnew两个多数派的确认;


Follower收到Cold∪Cnew的日志后更新本地日志,并且马上就以该配置作为自己的成员配置;


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


2、提交Cnew

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


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



可以看到,Raft算法将成员配置的变化也作为一条日志,需要经过一轮Raft过程像应用操作一样只要大多数节点确认了就肯定不会出出脑裂了。注意Follower收到配置后马上就变更,而不需要等Leader下次发送commitIndex的时候才提交,这点是和正常应用提交不一样的地方。


这里不去做详细的证明,官方有详细说明,我们可以看下几个异常,还是以上面的例子,当前集群有3个节点:1、2、3,现在要增加2个节点:4、5,假设当前Leader为1,具体过程如下:


1)节点1收到成员变更的请求,生成一条日志类型为成员变更,内容为:1、2、3、4、5,节点1将这条日志先保存到本地,但不马上更改成员配置;

2)节点1将上述日志同步给2,3,4,5四个节点;

3)2、3、4、5节点收到配置后做2件事:追加日志,马上变更自己的成员配置为:1、2、3、4、5;

4)节点1只要收到2个以上节点回复马上将自己成员配置为:1、2、3、4、5.


我们看有哪些异常:

如果有1-2过程中失败了,则整个过程就算失败,则需要管理员重新发起成员变更操作;

如果第3步只有1个节点即不是集群多数节点收到变更,这个时候节点1挂了,如果收到日志的先发起选举则有可能推进这条日志,否则就不成功,即有可能丢失;

如果在第4步节点1挂了的话,新集群肯定是有这个配置的,因为根据日志最新原则,新选举出来的Leader肯定包含上面成员变更的日志。



三、其它问题

1、新加的成员入无日志

开始的时候新的服务器可能没有任何日志条目,如果它们在这个状态下加入到集群中,那么它们需要一段时间来更新追赶,在这个阶段它们还不能提交新的日志条目,这个时候节点没有投票权,有的文章说叫Catch-Up。


2、移除不在 Cnew 中的服务器可能会扰乱集群

主要是移除的情况,设想一个集群有5个节点:1,2,3,4,当前Leader是2,现在要把1移除掉,假如2已经将新的成员配置:2,3,4已经同 步给3和4了,如果配置不发给1,因为2认为集群中1不存在了,所以不会向1发心跳,而1没收到2的心跳,会增加自己的Term发起选举,其它成员收到后会退化成Follower,不过不会给1投票,因为1的日志不是最新的,不过这会影响集群的可用性。


针对这个问题,官方给的是PreVote,即在发起投票的RequestVote RPC请求之前再发一个PreVote请求,只有通过这个才正式发起RequestVote,这样可以大大提升系统的可用性。其实还有1个更简单的办法,直接将1关机或者将服务程序Kill掉。