花了好长时间终于吧这篇笔记肝出来了QAQ,最近确实好久没做笔记了(虽然这篇笔记是为毕设服务的),后面估计会多发几篇笔记,鞭策下自己吧QwQ。
Raft是更易理解的共识算法,相比于Paxos来说,Raft通过两个主要操作来使得问题更佳简化:
Raft的易理解性,论文中也有调查分析证明。通过对学习Paxos和学习Raft的学生考试对比,学习Raft的考试得分普遍高于Paxos的考试得分。
相同的初始状态状态A + 相同的一系列指令 = 相同的结束状态状态B
指所有节点在相同的状态A下,执行一系列指令后,都能得到一致的状态B。这保证了分布式节点的一致性。
节点之间使用远程过程调用(RPC)进行,通信。Raft的RPC有三种:
term
(任期),term
(任期)是递增的,且每个term
(任期)中的时间是随机的(论文中给出的时间是150ms-300ms)。假设当前节点中有leader,leader会不断向其他节点发送心跳包(一种特殊的AppendEntries RPC),当flower节点收到这个RPC时,会重置该任期的时间,这样就不会出现任期内时间周期超时的情况。
假设一开始没有Leader,这时候当有follower当前term的时间周期结束时就会自动转变为candicate,并向集群中其他节点发起选举请求。
选举过程如下:
当节点当选leader时,对于客户端发送的每个命令,leader都会储存在对于的日志条目(log entity)中,对于日志条目基本信息如下:
当客户端请求执行指令,客户端就会发送到Raft集群的其中一个节点中,如果发送到follower节点,就会将这个指令转发到leader节点中。
如果发送到leader节点,leader节点会生成一条日志(log entity),并将这条日志通过AppendEntries RPC向follower节点追加日志。
需要注意的是,此时日志并没有真正提交,follower节点收到leader发来的AppendEntries RPC请求后,复制该日志完成后向leader节点发送响应回复RPC,当有半数节点回复时,此时leader节点就可以将这条日志进行提交。
未回复的follower节点有两种情况:
此外,可能还存在leader节点宕机的情况。如果leader宕机一段时间,导致follower节点任期超时,就会触发leader选举流程。
这时选举出的新leader会出现和follower日志不一致的情况,这时会通过follower强制复制leader日志的手段解决问题。
为了处理边界问题,Raft论文中独立开辟了第三个子问题——安全性,该子问题主要分为四个部分:
我们来看下面这张图:
这张图种我们不难看出,follower节点中有很多节点不具备完整的日志,是因为日志复制过程中只要日志复制到了大多数节点就可以提交。
假如这一时刻leader突然宕机了,日志缺失的节点当选leader,是十分危险的。所以要进行选举限制。
如果candidate上的最后一个日志相比较与follower上的最后一个日志不是最新的(即使follower上最后一个日志是未提交的),那么follower将拒绝该candidate的选举请求。
这个新是这么定义的:
有了这样的规则,就能保证选举出来的leader日志是最新的。
我们分析一下上图:
这里有个问题:(c)时刻中日志2可以提交吗?
假设日志2可以提交,那么(d)时刻,S5再次当选leader,然后把日志3覆盖复制到了所有节点中。这样日志2就被覆盖掉了,是十分危险的
leader只能提交自己任期内的日志,不会提交非自身任期内的日志。
(但非自身任期内的日志时可以复制的(因为复制是安全的),且提交自身任期内日志时可以顺便把非自身任期内日志提交了,这个个人理解是因为日志时有序的,当新日志提交了,没提交的老日志就默认提交了,这个情况是安全的,不会导致数据不一致的情况。)
所以说,(d)时刻的情况是不存在的,即使S5再次当选leader,也要生成一个任期号为5的日志并提交,才能把别的节点未提交的日志覆盖了,否则(d)时刻这种情况也不一定是安全的。
(e)时刻的情况是合法的,S1将日志4复制到了大多数节点,此时已经构成大多数,S1可以把日志4提交了,这样日志2也就一并提交了。
这种情况是比较简单的,follower或candidate宕机的情况会导致rpc包未被接收。这是只用重复发送rpc包,直到对方节点响应位置。(rpc请求都是幂等的)
Raft系统整体不受时间影响。也就是说rpc包接收顺序并不会影响日志顺序。
当设置选举超时时间时,主要要考虑两个因素:广播时间(个人理解为延时时间)和平均故障时间。
然而,一般平均故障时间都会比较长,所以不用太考虑这个因素影响,主要考虑广播时间。
总结来说就是:
广播时间 << 选举超时时间 << 平均故障时间
分布式集群成员变更过程中,可能会发生leader中途宕机的,造成新旧配置选举出两个leader的情况。这种情况就是分布式系统中的脑裂问题。如下图所示。
图中为三节点扩容至五节点的某一时刻,绿色为旧配置节点,蓝色为新配置节点。此时leader突然宕机恢复。其中旧配置中集群数为3,还在旧配置的节点为1和2,两票可以在旧配置中当选出一个leader。新配置节点数为5,且切换到新配置的节点为3/4/5,三票可以在新配置中当选出一个leader。就会产生两个leader当选的情况。
Raft为了防止这一情况发生,在旧配置和新配置切换过程中加入一个过渡的配置——联合一致(C oldnew)。
联合一致状态下,投票规则如下:
在联合一致状态的节点,投票必须要通过新配置和旧配置的大多数节点才能成功。
这样就能避免脑裂问题了。
还是这张图,但这次不是从旧配置直接切换到新配置,而是进入联合一致状态。那么这个时刻leader宕机恢复。重新开始选举。
节点12之间可以产生一个leader,我们假设节点1当选leader。回到节点3/4/5中,假设节点3发起选举,在新配置中,节点3可以拿到节点4/5和自己三票,构成新配置投票的大多数。但是在旧配置中,节点3拿不到节点1/2的选票,因为节点1当选了leader,所以节点三选举失败。通过联合一致状态,就解决了脑裂问题。
新增节点时,因为新节点日志相比leader落后很多。此时可以先将新节点设置为只读(没有投票权,不参与日志计数),然后再复制节点,等到新节点日志追上leader后便可发起集群成员变更。
当集群成员缩减时,因为leader可能就是要缩减的节点,leader要在新配置配置完成后自动退位。此时他可以发送Cnew日志,但是日志计数是不计自身。
为了避免下线的节点超时发起选举,影响集群运行。因为leader不会继续发送心跳包给下线的节点,造成要下线的节点任期超时发起选举。
解决方法:服务器会在确信集群中有leader的情况下忽略选举请求。
具体方式:如果节点在最小选举超时时间内收到RequestVote RPC是,不会对选举请求进行投票,即使发起选举的节点任期号更大,也不会根据选举请求中的任期号更改自己的任期号。这样节点必须等待一个最小选举参与时间才能处理选举请求,leader可以通过不断发送心跳包来维持集群正常运作,不会被即将下线的节点发起的选举请求干扰集群。
本篇笔记是根据参考文献里的内容,以及Raft论文进行的个人对Raft相关内容的一篇知识梳理,知识量较大。虽然本人对相关知识进行了查阅论文考证,但是初学Raft算法,个人理解能力有限,如果有不正确的地方还望指出,感谢~
知乎 —— raft共识算法 - Leader选举
csdn —— raft算法详解
csdn —— 分布式一致性协议:Raft算法详解
bilibili —— 解读共识算法Raft
github —— 寻找一种易于理解的一致性算法(扩展版) —— 中文翻译