“状态机提供确定性转移语义,复制日志提供共同命令前缀;Paxos、Raft与Viewstamped Replication是日志层的具体协议。状态机复制由此支撑高可用键值存储、配置服务、数据库…”
形式陈述
Raft 在固定配置的crash-stop或带持久存储的 crash-recovery 节点间实现复制日志,等价于按日志索引连续解决共识实例。节点在 follower、candidate 与 leader 角色间切换;任期是单调递增的逻辑编号,收到更高任期消息便更新任期并退为 follower。
每个节点在回复相关 RPC 前持久化 currentTerm、votedFor 与日志条目 (term,command)。commitIndex 可重新向 leader 学习;lastApplied 则必须与应用状态一起解释。若应用状态完全易失,可以重建后重放;若应用状态已持久化,就必须保存匹配的应用位置,或提供等价的去重保证,不能把位置归零后再次执行已持久化的扣款。若重启后遗忘任期或投票,节点可能在同一任期投两票;若遗忘日志,选举时便会谎报自己的证据,两者都会破坏安全证明。
选举与日志复制
Candidate 递增任期并请求投票。节点每任期最多投一票,而且只投给日志至少与自己一样新的候选者;新旧关系按 (lastLogTerm,lastLogIndex) 字典序比较,先比较最后条目的任期,同任期才比较索引。候选者取得多数quorum后成为该任期 leader。任意两个多数相交,加上一任期一票,推出每任期至多一个 leader。
Leader 用 AppendEntries(prevLogIndex,prevLogTerm,entries,leaderCommit) 复制日志。Follower 只有在 prevLogIndex 处任期匹配时才接受;若新条目与本地同索引条目任期冲突,就删除该处起的后缀再追加。由此得到日志匹配性质:若两份日志在同一索引有相同任期,则该索引及之前的全部条目相同。
提交规则
当前 leader 只能用“已复制到多数”直接推进当前任期条目:若某个 matchIndex 至少为 log[N].term == currentTerm,leader 才把 commitIndex 推到 leaderCommit 学到提交位置,并严格按索引顺序应用 committed 前缀。
直觉
Raft 让选举证据与复制证据在多数交点相遇。日志新旧限制使含有更高任期或更长同任期前缀的节点阻止过时候选者获多数,AppendEntries 检查再把 leader 的前缀向 followers 扩散。两条规则结合可推出 leader completeness:一旦条目按 Raft 规则 committed,每个更高任期 leader 都含有它;再结合日志匹配,任何两个节点在同一索引应用的命令相同。
“按 Raft 规则”中的当前任期条件不可省略。旧任期条目即使后来出现在多数日志上,少数节点仍可能持有同索引、更高任期的冲突条目;该节点的日志在下一次选举中反而更新。当前 leader 先提交一个本任期条目后,支持它的多数拥有不低于本任期的日志证据,任何缺少该前缀的候选者都无法再凑齐多数。于是旧前缀随本任期条目间接提交。
例子与边界
五节点集群可容忍两个 crash-stop。旧 leader 网络隔离后可能继续接收请求,却无法获得三个节点的复制确认,因此不能提交;它稍后收到更高任期消息便退位。安全性不依赖超时长短,分区只会让不含多数的一侧失去进展。
Figure 8 式轨迹说明旧任期边界。任期 2 的 leader 只把
恢复还有一个与选举无关的窗口:日志 10 已执行并把余额从 100 扣至 90,节点随后在保存应用位置前崩溃。重启若从旧 lastApplied=9 重放同一扣款,会得到 80;Raft 日志只出现一次请求也不能阻止应用层重复效果。应原子地持久化状态变更与应用位置,或从一致快照重新构建两者。
若选举实现只比较日志长度而忽略最后日志任期,较长却过时的候选者可能获胜;若比较顺序颠倒,同样会破坏 leader completeness。快照必须携带 last included index/term,才能继续参与相同的匹配与新旧判断。
推论与应用
Raft 用任期、领导者选举、投票限制和 AppendEntries 具体实现复制日志的提交接口,再由复制状态机应用 committed 前缀。这一接口可写成实现状态到抽象日志状态的精化关系:冲突尾部和重试属于实现行为,已提交前缀必须映到同一抽象历史。模型检查可以发现遗漏当前任期条件或持久化顺序后的短反例,却不能替代抽象映射。
在固定配置中,
成员变更要用 joint consensus 让过渡期决定同时取得旧、新配置所需的 quorum,不能直接把成员列表瞬间替换。客户端去重、线性一致读、磁盘写入屏障与快照安装仍是完整服务的额外义务,不能从日志匹配性质直接推出。
在 nextIndex/matchIndex 记录。命令 payload、应用状态、客户端去重表及各记录的 bit 长度分别计费;日志修复、选举、重传与独立的后台心跳会增加开销,不能并入上述单条正常路径界。
复制协议之外的服务接口
读屏障与ReadIndex把上述“线性一致读是额外义务”展开为本任期提交、新读关联的多数确认、应用位置等待三道条件;旧leader本地读与当前leader应用落后分别给出反例。联合配置则保留本页固定配置证明,另行展开配置项追加即生效、J用双多数提交、N用新多数提交的交接轨迹。
快照与安装把last included index/term接到状态、应用位置、结果表和配置的同一截点;保留后缀需边界匹配,持久发布必须先于日志回收。会话序号去重负责不同日志槽位内的同一逻辑请求,不能误以为Raft日志匹配已经提供这项承诺。
参考资料
- Diego Ongaro and John Ousterhout, “In Search of an Understandable Consensus Algorithm”, USENIX ATC, 2014,pp. 305–319,§§5.2–5.4 and Fig. 8。
- Diego Ongaro, Consensus: Bridging Theory and Practice, PhD dissertation, Stanford University, 2014,Chs. 3–4。