可能是你能找到的最完整最详细的中文版的Raft算法说明博客
内容来源都是原生的论文,可以保证内容的可靠性,并且对论文里面的很多细节做了扩展说明
任期的概念 term
任期的概念:
- Raft 把时间分割成任意长度的任期(term),如图 5 所示。任期用连续的整数标记。
- 每一段任期从一次选举开始,一个或者多个 candidate 尝试成为 leader 。如果一个 candidate 赢得选举,然后他就在该任期剩下的时间里充当 leader 。在某些情况下,一次选举无法选出 leader 。在这种情况下,这一任期会以没有 leader 结束;一个新的任期(包含一次新的选举)会很快重新开始。
- Raft 保证了在任意一个任期内,最多只有一个 leader
个人理解:
- 在raft算法中,比较谁的数据最新有2个参考指标,任期和logIndex,任期大的节点,数据一定最新,任期一样的话,就要比较该任期内谁的MaxLogIndex最大了。引入任期的概念可以简化数据比较的精度。
任期的作用:
不同的服务器节点观察到的任期转换的次数可能不同,在某些情况下,一个服务器节点可能没有看到 leader 选举过程或者甚至整个任期全程。
任期在 Raft 算法中充当逻辑时钟的作用,这使得服务器节点可以发现一些过期的信息比如过时的 leader 。
每一个服务器节点存储一个当前任期号,该编号随着时间单调递增。
服务器之间通信的时候会交换当前任期号;
如果一个服务器的当前任期号比其他的小,该服务器会将自己的任期号更新为较大的那个值。
如果一个 candidate 或者 leader 发现自己的任期号过期了,它会立即回到 follower 状态。(所以说老leader如果发生了网络分区,后来接收到新leader的心跳的时候,比拼完任期之后,会自动变成follower。
如果一个节点接收到一个包含过期的任期号的请求,它会直接拒绝这个请求。
选举的发起流程:
- 每个Node启动的时候,初始化Role都是Follower,并且启动计时器,超时还没接收到消息(可以是Leader的AppendEntries RPC,也可以是 Candidate的Vote RPC)
Raft集群的启动选举
Raft 使用一种心跳机制来触发 leader 选举。当服务器程序启动时,他们都是 follower 。一个服务器节点只要能从 leader 或 candidate 处接收到有效的 RPC 就一直保持 follower 状态。Leader 周期性地向所有 follower 发送心跳(不包含日志条目的 AppendEntries RPC)来维持自己的地位。
如果一个 follower 在一段选举超时时间内没有接收到任何消息,它就假设系统中没有可用的 leader ,然后开始进行选举以选出新的 leader。选举过程
要开始一次选举过程,follower 先增加自己的当前任期号并且转换到 candidate 状态。然后投票给自己并且并行地向集群中的其他服务器节点发送 RequestVote RPC(让其他服务器节点投票给它)。
Candidate 会一直保持当前状态直到以下三件事情之一发生:
(a) 它自己赢得了这次的选举(收到过半的投票)
(b) 其他的服务器节点成为 leader
(c) 一段时间之后没有任何获胜者。这些结果会在下面的章节里分别讨论。
当一个 candidate 获得集群中过半服务器节点针对同一个任期的投票,它就赢得了这次选举并成为 leader 。
对于同一个任期,每个服务器节点只会投给一个 candidate ,按照先来先服务(first-come-first-served)的原则(注意:5.4 节在投票上增加了额外的限制)。
要求获得过半投票的规则确保了最多只有一个 candidate 赢得此次选举(图 3 中的选举安全性)。
一旦 candidate 赢得选举,就立即成为 leader 。然后它会向其他的服务器节点发送心跳消息来确定自己的地位并阻止新的选举。
在等待投票期间,candidate 可能会收到另一个声称自己是 leader 的服务器节点发来的 AppendEntries RPC 。
如果这个 leader 的任期号(包含在RPC中)不小于 candidate 当前的任期号,那么 candidate 会承认该 leader 的合法地位并回到 follower 状态。
如果 RPC 中的任期号比自己的小,那么 candidate 就会拒绝这次的 RPC 并且继续保持 candidate 状态。
第三种可能的结果是 candidate 既没有赢得选举也没有输:如果有多个 follower 同时成为 candidate ,那么选票可能会被瓜分以至于没有 candidate 赢得过半的投票。
当这种情况发生时,每一个候选人都会超时,然后通过增加当前任期号来开始一轮新的选举。然而,如果没有其他机制的话,该情况可能会无限重复。
Raft 算法使用随机选举超时时间的方法来确保很少发生选票瓜分的情况,就算发生也能很快地解决。为了阻止选票一开始就被瓜分,选举超时时间是从一个固定的区间(例如 150-300 毫秒)随机选择。这样可以把服务器都分散开以至于在大多数情况下只有一个服务器会选举超时;然后该服务器赢得选举并在其他服务器超时之前发送心跳。同样的机制被用来解决选票被瓜分的情况。每个 candidate 在开始一次选举的时候会重置一个随机的选举超时时间,然后一直等待直到选举超时;这样减小了在新的选举中再次发生选票瓜分情况的可能性。9.3 节展示了该方案能够快速地选出一个 leader 。
个人理解:
选举其实看其实PK3个指标:term、LastLogIndex、lastLogTerm,至少要不比自己小才会vote给你,实际上这并不能保证最新的数据
因为vote RPC里面,只有lastLogIndex和Term,可能leader里面的lastLog是未提交的状态,但是其它follower的状态是提交的。所以新一任leader上任之前做的第一件事就是发一个心跳,update lastCommitIndex
投票消息(候选者发起)
follow在一定时间内未接收到leader发过来的heartbeat,超时后自己状态成为候选者,并且向其它的所有节点发起一个投票消息
一个完整的投票消息包括4个参数
- 自己的term
- 候选者ID(自己的ID)
- lastLogIndex index of candidate’s last log entry
- lastLogTerm term of candidate’s last log entry
receiver节点:先判断term,再判断日志是否是最新的。至少任期以及日志记录,不比自己旧,才会投票给你。所以过时的节点不会得到大多数的投票。
启动投票
- 系统刚刚启动,所有节点的任期都是0,大家的role都是follower
- 一个启动的节点第一个触发未检测到心跳超时,自增任期为1,并且重新计时(投票开始时间),给自己投一票,然后向所有的其它节点发起投票
- 其它节点当前的任期都为0,且日志也没空,肯定会投票给它,而且这些节点因为收到了candidate的投票选举,清零自己的心跳空白等待时间,未超时前不会发起投票,从而避免多重投票导致无效投票的可能性
- 第一个发起投票的节点收到半数投票,成为leader。即使投票失败(半数投票不成功)也要等到自己投票超时时间,到了超时时间之后,再自增任期,发起新的一轮投票。
运行中发起投票
- 每次follower收到leader的一次HeartBeat,都会清零自己的心跳计时器,重新开始计时,如果当前心跳计时器超时了,仍然未收到leader的心跳,就会从follower变成candidate
- 自增当前任期,且开始计时(选举计时),向其它节点发起投票
- 其它节点会比较 任期和日志的序号,至少不能比自己的数据旧才会投票给第一个发起投票的节点
- 超过半数节点投票成功,才会成为leader,否则要等待选举超时,再发起第二轮投票
投票期间重新收到leader心跳
candidate之所以会发起选举,是因为没有收到leader的心跳,但是在选举期间又重新收到心跳会如何?
论文中描述,当重新受到leader的心跳时会判断term,至少不能比自己小,也就是说,即使是因为自己网络原因没有收到心跳而发起投票,也不会终止这次投票,因为老leader的term比现在的要小,自己是自增了一次的。
但是如果在投票等待期间,已经有新的leader产生,并且接收到leader的 appending的RPC时,candidate会放弃投票,因为term不小于当前candidate,说明这个leader不是老leader,要么和自己是同一个term的leader,要么比自己更新term的leader。
所以理论上存在某一个follower的节点因为网路延迟而发起leader申请,并且还有可能成功顶替leader的可能性,即使leader的功能正常,是这个follower自己的网络突然发生了延迟。