外观
Raft:选举、复制与提交
Raft 把共识拆成 leader 选举、日志复制与安全性约束。每个节点维护递增任期 term、该任期投给谁 votedFor,以及日志。term 和投票必须在回复相关 RPC 前持久化,否则重启后可能在同一任期投两票。
选举不是挑一个活着的节点
follower 在选举超时后增加任期,转 candidate,先投自己,再请求其他成员投票。节点只有在本任期尚未投票或已投给该候选者,并且候选者日志足够新时才能授票。日志新旧按 (lastLogTerm, lastLogIndex) 字典序比较:最后任期更大优先,任期相同才看长度。随机化超时减少分票。
五节点集群的多数是 3。停掉两个节点后多数仍是 3,不能把分母改成“当前能看见的节点”。两个多数派必相交,加上每任期只投一票,保证同任期不会选出两个 leader。不同任期的旧 leader 可能暂时不知自己已过期,必须依靠 term 检查阻止它产生新的有效决策。
AppendEntries 先检查前缀
leader 发送 term, prevLogIndex, prevLogTerm, entries, leaderCommit。follower 先拒绝旧 term,再验证前一个位置的任期;不匹配就拒绝。匹配后,若新 entries 与本地同位置任期冲突,删除冲突项及后缀,再追加新 entries。不能因一次短心跳无条件截断 follower 的全部额外后缀。
日志匹配性质是:如果两个日志在同一索引有相同任期,它们到该位置为止的前缀一致。它依赖正确的 leader 追加和前缀检查;“term 相同”不是可以对任何随意产生的日志应用的魔法。
flowchart TD
A[接收 AppendEntries] --> B{任期过旧?}
B -->|是| R[拒绝并返回当前任期]
B -->|否| C{前驱索引与任期匹配?}
C -->|否| R
C -->|是| D[仅从冲突位置截断并追加]
D --> E[更新已确认范围内的 commitIndex]
E --> F[按顺序执行状态机]
查看流程图文本
flowchart TD
A[接收 AppendEntries] --> B{任期过旧?}
B -->|是| R[拒绝并返回当前任期]
B -->|否| C{前驱索引与任期匹配?}
C -->|否| R
C -->|是| D[仅从冲突位置截断并追加]
D --> E[更新已确认范围内的 commitIndex]
E --> F[按顺序执行状态机]
实际实现用 nextIndex 管理下一次尝试位置、matchIndex 管理已知复制到的位置;它们不是同一个变量。配套模型为了可读性每次发送完整前缀,正确性逻辑保留,传输效率较低。
复制到多数不等于任意日志都能直接提交
leader 查找一个索引 N,满足多数 matchIndex >= N,并且 log[N].term == currentTerm,才能按这条规则推进 commitIndex。提交当前任期条目时,它之前的条目随之前缀一并提交。
为什么加任期条件?旧任期某项即使暂时复制到多数,也可能在后续合法选举路径中被替换。Raft 的 leader completeness 证明要求通过当前任期条目确认新的领导权与前缀。新 leader 常提交一个 no-op,让旧前缀获得安全的提交依据。配套测试 test_previous_term_not_committed_by_count_alone 展示这个限制。
持久与易失状态
| 状态 | 恢复需求 |
|---|---|
| currentTerm / votedFor / log | 回复前持久化;重启保留 |
| commitIndex / lastApplied | 按协议与快照恢复,不能越界执行 |
| nextIndex / matchIndex | leader 运行时维护,重新选举后重建 |
| KV / 去重结果 | 从已提交日志回放,或加载一致快照 |
如果快照把 KV 截到索引 100,却只记录 lastIncludedIndex=90,重启回放 91–100 可能重复产生效果。快照边界必须对应状态机真正已执行到的位置。
易失 lastApplied 归零只有在状态机也从空状态重建时才合理;若 KV 已独立持久化,就必须与它记录一致的应用边界,不能盲目从头重放外部效果。配套模型选择“状态与去重表清空,日志保留”的同进程模拟。所有节点重启后没有已知 commitIndex,新 leader 需显式提交本任期 no-op 才回放旧前缀。
阅读配套实现
labs/distributed/raft_model.py 的 Node.vote 对应投票规则,Node.receive 对应前缀检查,Cluster.replicate 对应当前任期提交条件,Node.apply 是状态机应用。Cluster.restart 模拟重启,但没有磁盘 I/O;完整边界见实验页。
自测:旧 leader 收到更高 term 的拒绝后应做什么?
更新 term,清除旧任期投票状态,转 follower,停止以旧 leader 身份回复需要领导权的请求。不能只忽略拒绝继续重试旧 term。
来源:Raft 扩展论文,第 5–7 节。建议结合多数派交互演示阅读。