节点角色

在一个集群中,节点的角色分为三种:

  1. 领导者:处理写请求、管理日志复制、心跳维持。
  2. 跟随者:群众节点,被动处理来自领导者的消息。
  3. 候选人:当领导者失联时,跟随者会升级成候选人,通知其他节点选票,如果获得大多数选票,晋升为领导者。

领导者维持

领导者会周期性的发送心跳信息来通知所有跟随者,跟随者在超时时间内收到心跳信息后会重置本轮的超时时间,如此反复,当超时时间耗尽仍未收到领导者的心跳,则该跟随者晋升为候选人发起投票。

选举流程

每个节点记录自己的任期term和随机超时时间timeout(150-200ms),注意随机是重点。

  • 节点timeout内没有收到来自领导者的心跳,则该跟随者晋升为候选人发起投票,并将自己的term自增。
  • 跟随者在当前term任期内只会投票给首个要求投票的候选人,投票后跟随者增加自己的term
  • 候选人timeout内得到大多数的投票后晋升为领导者,并发送心跳信息给其他节点
  • 其他节点收到新领导者的心跳信息后放弃所有仍存在的投票过程,并记录领导者信息

选举冲突

如果有两个节点同时发起投票,并各自取得了一部分节点的选票,由于无法满足大多数条件,会一直等待,直到timeout超时。在timeout后会随机延迟一段时间再次重新发起投票。

日志复制

  • 客户端发送写数据到领导者
  • 领导者日志写入parpare状态,并将日志发给所有跟随者
  • 跟随者将日志写入parpare状态,并反馈给领导者
  • 领导者得到大多数跟随者的parpare状态后,提交日志
  • 响应客户端写入成功
  • 通知跟随者提交刚才的parpare的日志

分区容错

当发生分区错误时,存在多数节点部分和少数节点部分,不存在相等数量情况,具体情况见下面的集群扩容部分。

领导者在多数节点部分

少数节点部分会进行重新选举,但由于得不到大多数投票,因此会一直无法选举成功。

领导者在少数节点部分

多数节点部分会发起新的领导者选举,带领该分区内的节点正常服务;
少数节点部分在进行日志复制时,由于得不到大多数从节点的确认,因此该领导者也无法提交日志。

当分区恢复时

由于新领导者的term更大,在收到两个领导者的心跳时,会选择term最大的领导者作为领导者,term小的领导者此时也会自动降级为跟随者,在更改领导者后也会复制新领导者的日志。

注意在这种情况下,少部分节点区域无法对外提供服务,大部分节点区域可以提供服务,相当于降级,基本可用。此时产生的数据不一致会在分区恢复后进行纠正,符合最终一致性。

集群扩容

在集群扩容时,如果发生网络分区,可能产生多主问题。
比如原集群有3个节点A,B,C,现在要扩容到5个节点A,B,C,D,E。
对于原集群的大多数是2,在扩容时,如果发生分区,A,B在一起,C单独在一起,新加入的D,E也分在了C部分,造成两个分区分别为A,B和C,D,E。
在A,B分区内,由于大多数是2,所以有领导者A
在C,D,E分区内,新配置的大多数是3,所以有领导者C
此时产生了双主,且双主都可以进行正常写操作。
在分区恢复时,两个领导者battle,新领导者胜出,旧领导者会降级为跟随者,复制新领导者的日志,造成日志丢失。
解决方案
每次扩容单节点扩容,上述问题的根本是新增加的D,E与分离的C构成了新的大多数,造成双主。
如果每次增加一个,由A,B,C变为A,B,C,D,C,D部分的新大多数是3,所以不会产生双主。
当A,B,C,D扩容完成后,再增加E即可。

动画演示

raft动画演示