本页目录

分布式 II · 共识与 Raft

对标:MIT 6.824 / Raft 论文(Ongaro & Ousterhout)/ DDIA 第 9 章 | 前置:dist-01(故障、一致性)、db-03(复制日志) 分布式系统的皇冠问题:一群会各自宕机、消息会丢的机器,如何对"发生了什么、按什么顺序"达成一致? 这叫共识(consensus)。它是分布式数据库、配置中心(etcd/ZooKeeper)、区块链的共同内核。这一页从共识为什么难(FLP 不可能)讲到 Raft——一个被刻意设计得"可理解"的共识算法,也是 [大 Project P03] 的核心。

学习层:分区之后,哪一边还能提交一条命令?

具体谜题:5 个节点被切成 3+2,少数派能不能继续写?

一个 5 节点 Raft 集群原本有 leader,网络分区把节点切成 3 个和 2 个。客户端请求 set x=7 时,哪一边可以选出新 leader、收集足够复制确认并推进 commitIndex?如果 leader 恰好在 2 节点一侧,答案会改变吗?先按多数派规则预测。

先预测,再展开:选择 3+2、2+2+1 或无分区,预测可提交命令的节点数、所需 ACK 数,以及分区恢复后旧 leader 的日志如何处理。

最小心智模型:任期、日志和多数派

Raft 把复制状态机拆成选主、日志复制和安全性。leader 为客户端命令分配任期与日志索引,follower 按前一条日志的任期/索引匹配后追加;只有被多数节点复制的前缀才可提交并应用到状态机。

形式机制与不变量

规模为 \(N\) 时多数派为 \(q=\lfloor N/2\rfloor+1\);任何两个多数派相交,因此两个不同已提交值不能各自被安全地提交。日志匹配不变量是:若两个日志在同一索引和任期相同,则此前缀也相同;leader completeness 要求已提交条目出现在后续任期 leader 的日志中。Raft 牺牲分区少数派可用性换取线性一致提交。

反例与失效边界

没有多数派时,节点可以继续接收本地请求,却不能安全地宣布提交;异步网络下无法从“消息很慢”确定“节点已崩溃”,FLP 说明确定性终止保证需要额外时序假设。选主安全也不等于客户端请求执行恰好一次,重试仍需请求 ID 或幂等设计。

迁移任务:从 quorum 推进到真实实现

在 L06 Raft 实验里注入 3+2 分区、延迟和丢包,逐条记录 term、nextIndex、matchIndex 与 commitIndex;再把 trace 接到 P03-A 的复制日志接口。dist-01 的因果和 CAP 分析提供解释框架,但不替代 L06 的真实选举与日志复制。

交互实验:Raft 多数派、选主与提交

无 JavaScript 时的静态读法:默认 N=5,所以 quorum=3。3+2 分区时,3 节点侧可以选 leader 并取得 3 份日志(leader+2 followers)提交 set x=7;2 节点侧最多得到 2 份,必须停在未提交状态。2+2+1 时没有任何分区块达到 3,全部不能提交。若旧 leader 在少数派,恢复后会以更高任期的多数派日志为准,冲突后缀被回退而非双重提交。

分区 最大活动组 quorum 能提交? 日志动作
无 5 3 可以 复制并推进 commit
3+2 3 3 3 节点侧可以 少数侧停摆
2+2+1 2 3 不可以 等待重连

1. 共识问题与它的不可能性

共识:多个节点各有一个提议值,要达成一致——选出一个值,满足:一致(所有正常节点选同一个)、有效(选的是某人提议的)、可终止(最终能选出)。看似简单,却是分布式最难的问题。

FLP 不可能定理(理论基石):在异步网络(消息延迟无上界)里,哪怕只有一个节点可能崩溃,没有确定性算法能保证在有限时间内达成共识。为什么:无法区分"节点崩溃"和"消息只是很慢"(dist-01 的核心困难),于是算法可能永远等待。

这不是说共识没法做——而是说必须放松某个假设:实际系统用超时(假设网络"大部分时候"够快、用 timeout 探测故障)来绕过 FLP,换取"几乎总能终止"。理解 FLP 让你明白:所有实用共识算法都在'安全性绝不违背、活性靠网络配合'之间走钢丝——Raft 也不例外。

2. 状态机复制:共识用来干什么

共识的杀手级应用是复制状态机(RSM):让多台机器维护同一个状态(如一个 KV 存储),只要它们从同一个初始状态出发、按同一顺序执行同一串命令,就会保持一致。问题于是归结为:让所有副本对"命令的顺序"达成共识——即对一个复制日志达成一致:每个节点的日志是同样的命令序列,各自回放即得一致状态。

这就是那条主线的第三次登场(🔗 os-03 文件系统日志 → db-03 数据库 WAL → 这里的 Raft 日志):"把状态变更写成一条不可变的、全序的日志,谁按日志回放谁就得到一致状态"——可靠系统的元思想在三个层次反复出现,这是本站刻意编织的主线,到这里收束。

3. Raft:为可理解而设计的共识【机理级】

Raft 三态机 Follower↔Candidate↔Leader + 任期/心跳/选举超时。

图 dist-02.2Raft 三态机 Follower↔Candidate↔Leader + 任期/心跳/选举超时。

日志复制:Leader 追加→多数派确认→提交→应用。

图 dist-02.3日志复制:Leader 追加→多数派确认→提交→应用。

Paxos 是第一个正确的共识算法,但出了名地难懂。Raft 明确以"可理解性"为设计目标,把共识拆成三个相对独立的子问题:

① 领导者选举(Leader Election)

② 日志复制(Log Replication)

③ 安全性约束

Raft 的正确性口诀:任何时刻至多一个 Leader;已提交的日志永不丢失、永不改变顺序。这两条靠"多数派交集"贯穿始终。

4. 从共识到生产系统

5. 练习与要点

5 节点多数派交集:任两个多数派必相交(防脑裂)。

图 dist-02.15 节点多数派交集:任两个多数派必相交(防脑裂)。

例 1(多数派为什么是关键) 5 节点集群,为什么"提交需 3 个确认"能容忍 2 个节点崩溃、且不会脑裂?——证明任意两个多数派必相交,这是理解 Raft 全部安全性的一把钥匙。(也解释了为什么集群通常是奇数节点。)

例 2(选举超时随机化) 若所有节点选举超时相同会怎样(选票僵持反复)?随机化如何解决?——随机打破对称,algo-03 的思想在分布式再现。

例 3(脑裂推演) 网络分区把 5 节点切成 3+2,推演哪边能选出 Leader、能否提交、分区恢复后如何合并——验证 Raft 在分区下"少数派停摆、多数派继续"正是 CAP 的 CP 选择(dist-01 收线)。\(\blacksquare\)

▶ 实验 L06(Raft 选主 + 日志复制):labs/L06-raft/ —— 单机多协程模拟网络(含丢包/延迟),实现选举 + 日志复制的核心。跑在 Mac(Python)。这是 P03 的最小可运行前身。


📋 大 Project P03(第一阶段)· Raft 共识库

教师版作业说明书,不提供完整解。 P03 分两阶段随 dist-02/03 展开,最终是一个分片的、线性一致的分布式 KV。第一阶段只做可复用 Raft 库。

P03-A · Raft 共识库

  • 学习目标:把 Raft 的三个不变量写进代码和测试:任期单调、日志匹配、已提交日志不丢。
  • 教师提供:单机多节点网络模拟器、可控随机种子、丢包/延迟/分区/崩溃重启故障注入、公开测试分层(election/basic log/persistence/snapshot)。
  • 学生任务:① 领导者选举与心跳;② 日志复制、commit index、apply channel;③ 持久化当前任期、投票、日志并支持崩溃恢复;④ 快照与日志压缩。
  • 接口约束:Raft 模块只通过 Start(command)、ApplyMsg、持久化接口与上层交互;不得让 KV 逻辑泄漏进 Raft;所有 RPC handler 必须在任期变化时立即降级。
  • 验收测试:稳定网络 3/5 节点能选主并提交;少数派分区不能提交;leader 崩溃后新 leader 继续提交;节点反复崩溃重启后日志一致;快照后旧日志截断仍能恢复。
  • 评分重点:选举 20%,日志复制 30%,持久化 20%,快照 15%,故障测试与调试日志 15%。
  • 教师提示:要求学生在报告里列出 Raft 不变量和对应代码位置;这比“测试过了”更能检验理解。

下一页:分布式 III——分布式事务与大系统案例:跨机器的原子操作,以及 MapReduce/一致性哈希/大规模存储怎么落地。