相比 PBFT 协议,RAFT 协议是一种容忍CFT(Crash Fault Tolerance)类型的协议,即不考虑节点之间存在作恶的情况。RAFT 协议中主要包括 leader、follower、candidate 三个角色,下面按照协议的运行方式,首先从如何选取 Leader 这一步骤开始讲起。
Leader 选择
Raft 协议中使用心跳(heartbeat) 的机制触发 leader 选择。当服务器集群启动时,所有的节点都以 follower 的身份启动。当一个节点启动时,首先随机在 150ms~300ms 之间选定一个超时时间,然后等待 leader 发送的 heartbeat。Leader 节点应该定时的向所有的 follower 节点发送 heartbeat 节点以证明自己处于存活状态。如果一个 follower 节点在其超时的时间间隔内还是没有收到 heartbeat,则称之为 election timeout(选举超时)。此时该 follower 假定 leader 可能出错,此时进入 leader 选举阶段。
进入选举阶段,一个 follower 将自己当前的 term(类似于PBFT 协议中的 view) 增加 1,然后切换为 candidate 身份。随后节点向自己投一票,同时向其他 follower 节点发送 RequestVote RPC 以请求它们向该节点投票。在等待其他节点的投票期间,这个节点一只保持自己 candidate 的身份,但是,如果下列三件事情发生,其身份会发生变化:
-
该节点在本轮投票赢得选举。
-
该节点在本轮选举期间收到来自 leader 的消息。
-
选举超时,没有选出 leader。
一个 candidate 如果能得到大部分节点的选票,那么就能成为 leader。当一个candidate 成为 leader 后,就会立刻向其他 follower 发送 heartbeat 消息以告知自己的 leader 身份以防止其他节点再次进行选举。
节点在向 candidate 投票时,Raft 协议规定在一个 term 只能给一个节点投票,一般按照先来先投票的原则。而成为 leader 的必须收到大多数节点的投票,获得过半票数的设计保证一个 term 最多产生一名 leader。
当一个节点是 candidate 时,如果收到其他节点自称 leader 的消息,则检查消息中的 term,如果leader 的 term 大于等于本节点的 term,则认同发送节点的 leader 身份,并且转至 follower 身份,否则该节点继续保持自己 candidate 的身份。
一个节点可能在一个 term 情况下,没有选举成功,但是也没有选举失败,这是因为多个 candidate 在本次 term 中对票数进行分流。为了解决这个问题,raft 采用随机选举超时的机制,即一个节点如果 candidate 失败之后,再次随机选择一个超时时间,如果这段时间内没有收到新的 leader 的 heartbeat,那么继续保持自己 candidate 身份并继续参加选举。
日志复制(Log Replication)
当一个节点成为一个 leader 之后,它便开始处理客户端的 request,每一个 request 都包含一条需要被执行的命令。当 leader 收到一个 request 时,leader 便将该命令当做一个新的 entry 放入到 log 中,随后将这条命令也告知其他 follower。当一个 entry 被安全地复制后,leader 将执行这一条 entry 并且将执行结果反馈给 client。
log 的组织形式如下图所示,log 中的每一个 entry 都包含其对应的 term 、命令以及在 log 中的索引。log 中的 term 用来判检测log 的不一致问题。
leader 用来决策一个 entry 是否要被执行,而一条 entry 被执行前必须首先被 commit。一个 entry 被 commit 的前提是,leader 已经确保这一条 entry 已经被大多数的 follower 收到。当一条 entry 被 commit 之后,这个 entry 之前的所有 entry 也会被 commit。Raft 保证 commit 之后的 entry 是持久化的,并且最终会被所有的状态机执行。

Raft 中如何根据每一条log 的 index 和 term 来确认 log 的一致性呢?其规则如下:
- 如果两个不同 log 中的两个 entry 分别具有相同的 index 和 term,那么这两条 entry 肯定相同。
- 如果不同两个 log 的两个 entry 具有相同的 index 和 term,那么它们之前的所有 entry 也是相同的。
上述第一个规则正确的原因是,对于一个给定的 index,一个 leader 在一个 term 中最多创建一个 entry。
上述第二个规则正确的原因是,每个 leader 发送 log entry 时,每个 entry 都包含其在 leader 的 log 中的 index 和term,follower 将该 entry 放入自己的 log 之后,检查该 entry 的 term 和 index 是否和 leader 的 term 和 index 一致,如果不一致,follower 会拒绝该消息,并且请求重新复制 leader 的 log 。通过这样的措施, follower 的 log 必然可以和 leader 的 log 保持一致。
在 Raft 中,由于网络原因,leader 和 follower 的 log 消息肯定会出现不一致,有可能 leader 的 log 远远多于 follower 的 log ,或者 follower 的 log 比 leader 的还要多。而 Raft 协议中,始终让 follower 复制 leader 的 log 使得 follower 的 log 和 leader 的 log 保持一致,即如果 follower 的 log 和 leader 的log 不一致,那么 follower 的 log 会被重写。
为了维护 log 中的一致性,每个 leader 会为每一个 follower 维护一个变量 nextIndex,这个变量维护了下一次要发送给 follower 的 log 的 index。当一个节点刚刚成为 leader 时,每个 follower 的 nextIndex 会被设置为 leader 节点最后一条日志的后面一个位置。随后 leader 分别向每一个 follower 发送 nextIndex 指向的 log。 如果 follower 收到的 nextIndex 指向的 log 和本地的 log 不同,则 follower 会拒绝该消息,随后 leader 需要在通信过程中发现两者 log 最后一个匹配的位置,随后 follower 才会接受 leader 的 entry。