节点主动发起请求有如下特性:
- 只有 Leader 会主动发送
appendEntries
给其他节点 - 只有 Candidate 会发送
requestVote
给其他节点 - Follower 不会主动发送任何请求给其他节点,除非由于 Follower timeout 而切换为 Candidate。
节点接受请求有如下特性:
- 处理请求,只有在
term = currentTerm
情况下才可以正常进行处理请求 term > currentTerm
,当前节点必须转换为 Follower,然后继续以 Follower 角色继续处理请求- Follower 接受
appendEntries
以及requestVote
- Candidate 接受
term >= currentTerm
的appendEntries
,转换为 Follower,然后继续以 Follower 角色继续处理请求 - Candidate 不接受
term <= currentTerm
的requestVote
,因为 Candidate 给自身投票了 - Leader 是否接受
appendEntries
?term > currentTerm
,Leader 直接变为 Follower,然后以 Follower 角色继续处理请求term = currentTerm
,Leader 是否可以直接拒绝?
接受
appendEntries
,不用特意考虑是否是 Leader 见部分开源项目的实现,lni/dragonboat-raft.go#L1835 - Leader 是否接受
requestVote
?- 如果
requestVote
term > currentTerm
,那么 Leader 变为 Follower,并进行投票逻辑
如果
requestVote
term = currentTerm
,Leader 是否需要执行投票逻辑? hashicorp/raft#235 Leader 可能拒绝term = currentTerm
的投票Leader 的 lastLogIndex 小于 Candidate 的 lastLogIndex 时,Leader 投票给 Candidate,Leader 变为 FollowerCandidate.term = Leader.currentTerm && Leader.lastLogIndex < Candidate.lastLogIndex
Leader 也不能投票给 Candidate,因为 Candidate.term = Leader.currentTerm 会造成 term 在集群中的混乱。可能会有具有相同 term 不同 节点担任 Leader。接受requestVote
,不用特意考虑是否是 Leader 见部分开源项目的实现,lni/dragonboat-raft.go#L1835 - 如果
节点各角色下的超时时间(raft 论文中,只有一个超时定时器,这里的实现分成两个,一个 Follower 的超时定时器,一个 Candidate 的超时定时器)
- Follower 相关的超时时间应该要比 Candidate 超时时间长
- 否则 Follower 刚给 Candidate 投完票,但是由于 Follower 超时而变成了 Candidate
- 这样就会导致多个节点发起多次选举,而使 Leader 选举低效。
读到这里,我们应该意识到,并不是任何一个Follower都有资格成为Leader, 因为如果一个Follower缺少了上任Leader已经commit的日志, 那它是无论如何也不能做新的Leader的,因为这会导致数据的不一致。 1620秒入门Raft
send or handle rpc | Follower | Leader | Candidate |
---|---|---|---|
send AppendEntries |
No | Yes | No |
send RequestVote |
No | No | Yes |
receive AppendEntries |
Yes | lni/dragonboat-raft.go#L1835 | Yes |
receive RequestVote |
Yes | lni/dragonboat-raft.go#L1835 | Yes |
heartbeat time out | Yes | No | No |
election time out | No | No | Yes |
- 响应来自候选人和领导者的请求
- 如果在超过选举超时时间的情况之前都没有收到领导人的心跳,或者是候选人请求投票的,就自己变成候选人
- 如果接收到的 RPC 请求或响应中,任期号
T > currentTerm
,那么就令currentTerm
等于T
,并切换状态为跟随者(5.1 节) - 如果
commitIndex > lastApplied
,那么就lastApplied
加一,并把log[lastApplied]
应用到状态机中(5.3 节)
-
receive
heartbeat
orappendEntries
- 如果
term < currentTerm
, 则返回false
- 重置 Follower 的 timeout
- 如果
term > currentTerm
, 设置currentTerm = term
info: 应该以 Follower 角色继续执行
- 如果 Follower 的
log[prevLogIndex].term != prevLogTerm
, 则返回false
- 如果存在
N
,使得log[entries[N].logIndex].term != entries[N].term
, 删除log[entries[N].logIndex]
(执行循环) - 附加日志中尚未存在的任何新条目
- 如果
leaderCommit > commitIndex
则commitIndex = min(leaderCommit, min(entries[*].logIndex))
- 如果
commitIndex > lastApplied
,那么就lastApplied
加一,并把log[lastApplied]
应用到状态机中(5.3 节) - 返回
currentTerm
和true
- 如果
-
receive
requestVote
- 如果
term < currentTerm
返回 false (5.2 节) - 如果
term > currentTerm
, 设置currentTerm = term
,继续执行 - 如果
votedFor
为空或者为candidateId
,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节)info: 判断的是 commitIndex 和 lastLogIndex?
(votedFor == null or votedFor == '' or votedFor == candidateId) and (lastLogIndex > log.last().logIndex)
- 如果投票给 Candidate,那么重置 Follower 的 timeout
防止 Follower 过早进入选举
- 如果
-
timeout
- 如果在超过选举超时时间的情况之前都没有收到领导人的心跳,或者是候选人请求投票的,就自己变成候选人
- 在转变成候选人后就立即开始选举过程
- 自增当前的任期号(currentTerm)
- 给自己投票
- 重置选举超时计时器
- 发送请求投票的 RPC 给其他所有服务器
- 如果接收到大多数服务器的选票,那么就变成领导人
- 如果接收到来自新的领导人的附加日志 RPC,转变成跟随者
- 如果选举过程超时,再次发起一轮选举
- 如果接收到的 RPC 请求或响应中,任期号
T > currentTerm
,那么就令currentTerm
等于T
,并切换状态为跟随者(5.1 节) - 如果
commitIndex > lastApplied
,那么就lastApplied
加一,并把log[lastApplied]应用到状态机中(5.3 节)
-
receive
heartbeat
orappendEntries
- 如果
term < currentTerm
, 则返回false
- 如果
term >= currentTerm
, 设置currentTerm = term
,设置 role 为 Follower,并重置 Follower 的 timeoutinfo: 应该以 Follower 角色继续执行 如果接收到来自新的领导人的附加日志 RPC,转变成跟随者
- 如果
-
receive
requestVote
- 如果
term <= currentTerm
返回 false (5.2 节)如果
term = currentTerm
,但是 Candidate 已经投票给自己,所以不会在投票给其他节点 - 如果
term > currentTerm
, 设置currentTerm = term
,重置 Follower 的 timeout,继续执行info: 应该以 Follower 角色继续执行
- 如果
-
send
requestVote
- 发送选举前,执行如下:
- 自增当前的任期号(currentTerm)
- 给自己投票
- 重置选举超时计时器
- 发送请求投票的 RPC 给其他所有服务器
- 投票用的参数
lastLogIndex
以及lastLogTerm
是log
最新的一条,如过log
为空,均设置为 0 - 如果接收到的 RPC 响应中,任期号
T > currentTerm
,那么就令currentTerm
等于T
,并切换状态为跟随者(5.1 节)loop 选出最大的
T
,切换角色为Follower,重置Follower timeout,然后退出选举流程 - 如果接收到大多数服务器的选票,那么就变成领导人
- 如果接收到来自新的领导人的附加日志 RPC,转变成跟随者
- 如果选举过程超时,再次发起一轮选举
- 发送选举前,执行如下:
-
timeout
- 如果选举超时,且未成为
Leader
,则重新发起选举
- 如果选举超时,且未成为
- 一旦成为领导人:发送空的附加日志 RPC(心跳)给其他所有的服务器;在一定的空余时间之后不停的重复发送,以阻止跟随者超时(5.2 节)
- 如果接收到来自客户端的请求:附加条目到本地日志中,在条目被应用到状态机后响应客户端(5.3 节)
- 如果对于一个跟随者,最后日志条目的索引值大于等于
nextIndex
,那么:发送从nextIndex
开始的所有日志条目:- 如果成功:更新相应跟随者的
nextIndex
和matchIndex
- 如果因为日志不一致而失败,减少
nextIndex
重试
- 如果成功:更新相应跟随者的
- 如果存在一个满足
N > commitIndex
的N
,并且大多数的matchIndex[i] ≥ N
成立,并且log[N].term == currentTerm
成立,那么令commitIndex
等于这个N
(5.3 和 5.4 节) - 如果接收到的 RPC 请求或响应中,任期号
T > currentTerm
,那么就令currentTerm
等于T
,并切换状态为跟随者(5.1 节) - 如果
commitIndex > lastApplied
,那么就lastApplied
加一,并把log[lastApplied]应用到状态机中(5.3 节)
-
receive
heartbeat
- 如果
term < currentTerm
, 则返回false
- 如果
term > currentTerm
, 设置currentTerm = term
,重置 Follower 的 timeoutinfo: 应该以 Follower 角色继续执行
- 如果
term = currentTerm
,且 Candidate 的日志跟 Leader 一样新,或者 Candidate 比 Leader 日志更新,Leader 投票给 Candidate,Leader 变为 Follower
- 如果
-
appendEntries
- 如果
term < currentTerm
, 则返回false
- 如果
term > currentTerm
, 设置currentTerm = term
,重置 Follower 的 timeoutinfo: 应该以 Follower 角色继续执行
- 如果
term = currentTerm
todo
- 如果
-
receive
requestVote
:- 如果
term <= currentTerm
返回 false - 如果
term > currentTerm
, 设置currentTerm = term
,重置 Follower 的 timeoutinfo: 应该以 Follower 角色继续执行
- 如果
-
send
heartbeat
orappendEntries
- 发送
heartbeat
,参数prevLogIndex
以及prevLogIndex
是log
最新的一条,如过log
为空,均设置为 0 - 发送
appendEntries
,参数prevLogIndex
以及prevLogIndex
取得是entries.first().logIndex - 1
的那条日志 - 如果接收到的 RPC 响应中,任期号
T > currentTerm
,那么就令currentTerm
等于T
,并切换状态为跟随者(5.1 节)loop 选出最大的
T
,切换角色为Follower,重置Follower timeout,然后退出heartbeat
orappendEntries
- 返回的响应如果成功:更新相应跟随者的
nextIndex
和matchIndex
,如果因为日志不一致而失败,减少nextIndex
重试成功:
nextIndex[i] = nextIndex[i] + 1
,matchIndex[i] = 对于每一个服务器,已经复制给他的日志的最高索引值
失败:nextIndex[i] = nextIndex[i] + 1
- 发送
如果有一个节点由于网络问题,一直不能与集群中的其他节点互联,那么其自身就会不断的竞选选举,将自身的 currentTerm 加一。 突然这个节点的网络问题修复了,由于其任期比集群中的节点都要大,就会当选 Leader,但是 raft 的 Leader 不会接受其他节点的 Log, 从而导致其他 Follower 已提交的 Log 被 Leader 覆写。
- 如果 votedFor 为空或者为 candidateId,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节)
安全性:在 Raft 中安全性的关键是在图 3 中展示的状态机安全: 如果有任何的服务器节点已经应用了一个确定的日志条目到它的状态机中, 那么其他服务器节点不能在同一个日志索引位置应用一个不同的指令。 章节 5.4 阐述了 Raft 算法是如何保证这个特性的; 这个解决方案涉及到一个额外的选举机制(5.2 节)上的限制。
- Leader 在收到 投票请求 时,如果投票请求的 term 参数大于 Leader 的 currentTerm,Leader 必须立刻变成 Follower,并立刻返回?
Leader 必须立刻变成 Follower,并以 Follower 的角色继续后续的流程。