选举与成员变更
Leader 选举
节点启动时,默认处于
- 心跳间隔,主节点隔多长时间发送心跳信息
- 等待时间
(election timeout) ,如果超过这个时间仍然没有收到心跳,则认为主节点宕机。一般每个节点各自在150 ~300ms 间随机取值。
当一个节点在等待时间内没有收到主节点的心跳信息,它首先将自己保存的
-
赢得了多数的选票,成功选举为
Leader ;Candidate 节点需要收到集群中与自己term 相同的所有节点中大于一半的票数(当然如果节点term 比自己大,是不会理睬自己的选举消息的) 。节点投票时会采取先到先得的原则,对于某个term ,最多投出一票(后面还会再对投票加一些限制) 。这样能保证某个term 中,最多只会产生一个leader 。当一个Candidate 变成主节点后,它会向其它所有节点发送心跳信息,这样其它的Candidate 也会变成Follower 。 -
收到了
Leader 的消息,表示有其它服务器已经抢先当选了Leader ;在等待投票的过程中,Candidate 收到其它主节点的心跳信息(只有主节点才会向其它节点发心跳) ,且信息中包含的term 大于等于自己的term ,则当前节点放弃竞选,进入Follower 状态。当然,如前所说,如果心跳中的term 小于自己,则不予理会。 -
没有服务器赢得多数的选票,
Leader 选举失败,等待选举时间超时后发起下一次选举。一般发生在多个Follower 同时触发选举,而各节点的投票被分散了,导致没有Candidate 能得到多数票。超过投票的等待时间后,节点触发新一轮选举。理论上,选举有可能永远平票,Raft 中由于各个节点的超时时间是随机的,实际上平票不太会永远持续下去。

成员变更
成员变更是在集群运行过程中副本发生变化,如增加
如果将成员变更当成一般的一致性问题,直接向
双多数派问题
一种方法是先停止所有节点,修改配置增加新的节点,再重启所有节点,但是这样服务起停时就会中断服务,同时也可能增加人为操作失误的风险。另一种方法配置好新的节点直接加入集群,这样也会出问题:在某个时刻使用不同配置的两部分节点可能会各自选出一个主节点。因为各个服务器提交成员变更日志的时刻可能不同,造成各个服务器从旧成员配置(Cold)切换到新成员配置(Cnew)的时刻不同。成员变更不能影响服务的可用性,但是成员变更过程的某一时刻,可能出现在
如下图:

图中绿色为旧的配置,蓝色为新的配置,在中间的某个时刻,
两阶段的成员变更
由于成员变更的这一特殊性,成员变更不能当成一般的一致性问题去解决。为了解决这一问题,

Leader 收到成员变更请求从Cold 切成Cnew ;Leader 在本地生成一个新的log entry ,其内容是Cold ∪Cnew,代表当前时刻新旧成员配置共存,写入本地日志,同时将该log entry 复制至Cold ∪Cnew 中的所有副本。在此之后新的日志同步需要保证得到Cold 和Cnew 两个多数派的确认;Follower 收到Cold ∪Cnew 的log entry 后更新本地日志,并且此时就以该配置作为自己的成员配置;- 如果
Cold 和Cnew 中的两个多数派确认了Cold U Cnew 这条日志,Leader 就提交这条log entry ; - 接下来
Leader 生成一条新的log entry ,其内容是新成员配置Cnew ,同样将该log entry 写入本地日志,同时复制到Follower 上; Follower 收到新成员配置Cnew 后,将其写入日志,并且从此刻起,就以该配置作为自己的成员配置,并且如果发现自己不在Cnew 这个成员配置中会自动退出;Leader 收到Cnew 的多数派确认后,表示成员变更成功,后续的日志只要得到Cnew 多数派确认即可。Leader 给客户端回复成员变更执行成功。
异常分析:
- 如果
Leader 的Cold U Cnew 尚未推送到Follower ,Leader 就挂了,此后选出的新Leader 并不包含这条日志,此时新Leader 依然使用Cold 作为自己的成员配置。 - 如果
Leader 的Cold U Cnew 推送到大部分的Follower 后就挂了,此后选出的新Leader 可能是Cold 也可能是Cnew 中的某个Follower 。 - 如果
Leader 在推送Cnew 配置的过程中挂了,那么同样,新选出来的Leader 可能是Cold 也可能是Cnew 中的某一个,此后客户端继续执行一次改变配置的命令即可。 - 如果大多数的
Follower 确认了Cnew 这个消息后,那么接下来即使Leader 挂了,新选出来的Leader 肯定位于Cnew 中。
两阶段成员变更比较通用且容易理解,但是实现比较复杂,同时两阶段的变更协议也会在一定程度上影响变更过程中的服务可用性,因此我们期望增强成员变更的限制,以简化操作流程。两阶段成员变更,之所以分为两个阶段,是因为对
如果增强成员变更的限制,假设
一阶段成员变更:
- 成员变更限制每次只能增加或删除一个成员(如果要变更多个成员,连续变更多次
) 。 - 成员变更由
Leader 发起,Cnew 得到多数派确认后,返回客户端成员变更成功。 - 一次成员变更成功前不允许开始下一次成员变更,因此新任
Leader 在开始提供服务前要将自己本地保存的最新成员配置重新投票形成多数派确认。 Leader 只要开始同步新成员配置,即可开始使用新的成员配置进行日志同步。