你要用三台机器共同维护一份配置(或一份键值)。客户端写成功之后,任何一台宕机、任何一次网络抖动,都不能让「已经成功」的写在别的副本上消失或变成另一条命令。这件事乍看像「多备份一下」,做对却意味着在异步网络里解决共识(Consensus)。
Raft(Ongaro & Ousterhout, USENIX ATC 2014)把共识问题拆成可单独理解的几块:谁当领导者、怎么复制日志、哪些规则保证永不破坏已提交数据。它故意把「工程师能正确实现」当作一等设计目标,而不是先追求理论上的最大并发。
本文是入门旗舰:场景建直觉、图钉机制、少量公式钉不变量,最后用专章谈近年研究分叉。etcd 的 PreVote、ReadIndex、ConfChange V2 等工程补丁不在此展开,见 Raft 深度重写。协议选型与实测见 共识工程权衡。
本文是分布式系统百科的共识阅读入口之一。 正式机制以 ATC 2014 论文与 Ongaro 博士论文为准;工业扩展标注出处等级。
目标 去哪读 先建立正确心智模型 本篇 论文缺口与 etcd/TiKV 工程 raft-deep Raft vs Multi-Paxos vs EPaxos 17 Flexible / EPaxos 平行路线 14-epaxos
一、问题:三台机器如何对同一条写达成一致
1.1 坏结局场景
客户端 SET x=1,节点 A 返回成功。随后 A
宕机。客户端读到 B 上还是旧值,或更糟:读到 C 上是
x=2,那是另一次未对齐的写。多副本若没有全局唯一的操作顺序,应用层无论怎么重试都会面对互相矛盾的历史。
1.2 复制状态机(Replicated State Machine)
共识算法通常不直接同步「当前状态」,而是同步一份有序日志。只要各节点日志的已提交前缀相同,按相同顺序应用到确定性状态机,最终状态就相同。
需要同时关心两件事:
- 安全性(Safety):绝不能在同一日志位置对两个不同命令都称「已提交 / 已应用」。
- 活性(Liveness):在多数节点存活且最终能够通信时,系统应能继续接受请求。
FLP 不可能结果(Fischer, Lynch, Paterson, 1985)断言:在完全异步模型下,即使只有一个节点可能崩溃,也不存在确定性算法同时保证 Safety 与 Liveness。Raft 的工程折中是:Safety 绝对优先;活性依赖「随机化选举超时 + 多数派最终连通」这类实践假设。详见 FLP 与不可能性。
1.3 Raft 如何拆问题
| 子问题 | 英文 | 本文责任 |
|---|---|---|
| 领导者选举 | Leader Election | 第三节 |
| 日志复制 | Log Replication | 第四节 |
| 安全性 | Safety | 第五节 |
| 日志压缩 | Snapshot | 第六节(短) |
| 成员变更 | Membership Change | 第六节(短) |
相对 classic Paxos 叙述,Raft 把「谁发起提案」收敛为强领导者:稳态下客户端只跟 Leader 说话,Follower 被动复制。代价是 Leader 成为吞吐瓶颈——这正是第八节研究分叉的起点。
1.4 和「主从复制」差在哪里
很多人第一反应是:「不就是选个主库做异步/半同步复制吗?」差在提交语义。
| 典型主从复制 | Raft | |
|---|---|---|
| 主库故障 | 可能丢尚未同步的写;切换策略因产品而异 | 新 Leader 必须携带已 commit 前缀(Leader Completeness) |
| 「成功」含义 | 有时只表示主库落盘 | 表示条目已进入多数派稳定存储且可安全应用 |
| 脑裂 | 双主是常见事故模式 | 同 term 至多一合法 Leader;旧 Leader 遇更高 term 必须下台 |
| 规范完整性 | 常散落在产品文档与脚本 | 选举 / 复制 / 安全 / 成员变更写在同一套规则里 |
半同步复制可以逼近「多数确认再确认客户端」,但很少同时给出选举限制、旧任期提交规则与联合共识的一揽子不变量。Raft 的价值是把这些钉成可教学、可实现、可论证的一体协议。对照阅读:主从复制。
二、核心名词:任期、角色、多数派
2.1 任期(Term)
Term 是 Raft 的逻辑纪元:整数,单调递增;每次新选举开始时加一。每个 term 内至多一个 Leader。 节点收到更高 term 的 RPC 时更新本地 term 并退回 Follower;收到更低 term 的 RPC 则拒绝。
2.2 三种角色
stateDiagram-v2
[*] --> Follower
Follower --> Candidate: election timeout
Candidate --> Leader: wins majority votes
Candidate --> Follower: discovers higher term or loses
Candidate --> Candidate: split vote then new election
Leader --> Follower: discovers higher term
- Follower:响应 Leader / Candidate;超时未收到心跳则变 Candidate。
- Candidate:自增 term、投自己一票、广播
RequestVote;多数票则成 Leader。 - Leader:处理客户端写、发
AppendEntries(含心跳)、推进commitIndex。
2.3 多数派(Quorum)
\(N\) 节点集群的多数派大小为
\[ q = \left\lfloor \frac{N}{2} \right\rfloor + 1 \]
例如 \(N=3\) 则 \(q=2\);\(N=5\) 则 \(q=3\)。任意两个多数派集合的交集非空——这是后文 Leader Completeness 的几何直觉。
日志条目记为三元组 \((\textit{index}, \textit{term}, \textit{command})\)。Leader 还有:
commitIndex:已知可安全应用的最大 index;lastApplied:已应用到状态机的最大 index(不得越过commitIndex)。
三、Leader 选举:谁来协调写入
3.1 坏结局场景:所有人同时参选
若所有 Follower 使用相同超时、同时发现 Leader 失联,会同时变成 Candidate、互不投票 → 选票分裂(split vote),集群长时间无 Leader,写入全部卡住。
3.2 随机选举超时
每个 Follower 从区间(论文建议约 \(150\)–\(300\,\mathrm{ms}\),工程可按 RTT 标定)随机抽取 election timeout。超时未见心跳则:
- 当前 term 加一,角色变为 Candidate;
- 给自己投票,向同伴发
RequestVote; - 若获得多数票 → Leader,立刻发心跳抑制他人超时;
- 若发现更高 term 或现成 Leader → 退回 Follower;
- 若超时仍无结果 → 再开一轮选举(新 term)。
sequenceDiagram
participant F1 as Follower_S1
participant F2 as Follower_S2
participant F3 as Follower_S3
Note over F1,F3: Leader missing
F1->>F1: timeout first
F1->>F2: RequestVote term=T
F1->>F3: RequestVote term=T
F2-->>F1: grant vote
F3-->>F1: grant vote
Note over F1: becomes Leader
F1->>F2: AppendEntries heartbeat
F1->>F3: AppendEntries heartbeat
随机化把「同时超时」压成小概率事件;论文实验表明合理超时范围内选举通常一轮结束。
3.3 三节点选举走查
设集群 \(\{S_1,S_2,S_3\}\),原 Leader \(S_1\) 失联。
| 时刻 | \(S_2\) | \(S_3\) | 结果 |
|---|---|---|---|
| \(t_0\) | 超时抽到 \(170\,\mathrm{ms}\) | 抽到 \(240\,\mathrm{ms}\) | 仍为 Follower |
| \(t_0+170\,\mathrm{ms}\) | 升为 Candidate,term \(\leftarrow T+1\),自投 1 票 | 仍等超时 | \(S_2\) 发出
RequestVote |
| 收到 \(S_3\) 同意(假设日志足够新) | 授票后重置超时 | \(S_2\) 获 2 票=多数 → Leader | |
| 发送心跳 | 收到心跳,保持 Follower | 集群恢复可写 |
若 \(S_2\) 与 \(S_3\) 几乎同时超时且互不投票,本轮无人胜出;各自再次随机超时后重试。工程调参要点:
- heartbeat interval \(\ll\) election timeout 下界,否则健康 Leader 也会被误判死亡;
- election timeout 下界应明显高于典型心跳往返的尾部延迟;
- 跨地域部署若强行沿用机房内超时,会看到「伪故障选举」——这是运维问题,不是论文 bug。
3.4 投票规则里的安全阀门
Follower 或 Candidate 授予选票时至少检查:
- 候选人 term 不低于自己,且本 term 尚未把票投给别人;
- 选举限制(Election Restriction):候选人日志「至少与投票者一样新」。
「一样新」的比较顺序(论文定义):
- 先比双方日志最后一条的 term,更大者更新;
- term 相同再比最后一条的 index,更大者更新。
没有这条限制,日志严重落后的节点可能当选,随后用自己的日志覆盖已提交前缀——Safety 直接破裂。
用数字感受一次:
- 投票者最后一条为 \((index=10,\ term=5)\);
- 候选人最后一条为 \((index=12,\ term=4)\)。
按规则先比 term:\(4<5\),候选人更旧,即使 index 更大也不能获票。这避免「靠灌大量未提交的旧任期条目骗选」。
3.5 多数派交集直觉
Committed 条目一定落在某个多数派磁盘(或稳定存储)上;新 Leader 也由某个多数派选出。公共节点若遵守选举限制,就不会把选票交给「比已提交历史更旧」的候选人——这是第五节证明的脚手架。
四、日志复制:写入如何变成集群事实
4.1 一条写的正常路径
- Client 把命令发给 Leader(Follower 可重定向)。
- Leader 在本地日志末尾追加条目(当前 term)。
- 并行向 Follower 发
AppendEntries(可批量)。 - 多数节点持久化成功后,Leader 推进
commitIndex,回复 Client。 - 各节点把
lastApplied..commitIndex应用到状态机。
sequenceDiagram
participant C as Client
participant L as Leader
participant F as Follower
C->>L: Write command
L->>L: append local log
L->>F: AppendEntries
F-->>L: success
Note over L: majority reached
L->>L: commitIndex++
L-->>C: OK
L->>L: apply to state machine
F->>F: apply when notified
心跳是空的 AppendEntries,作用是重置
Follower 超时、捎带最新 commitIndex。
4.2 一致性检查与日志匹配
每条 AppendEntries 携带
prevLogIndex 与
prevLogTerm。Follower
必须在该位置有相同 term
的条目,否则拒绝。拒绝后 Leader
回退(论文逐步减一;生产实现常根据冲突 term 跳跃——见 deep
文)。
这保证 Log Matching Property:若两份日志在同一 index 上 term 相同,则该 index 及之前的所有条目都相同。
4.3 论文 Figure 8:为何不能「有多数就提交旧任期条目」
直觉上「多数副本已写入即可 commit」。对当前 term 的条目这通常成立;对旧 term 的条目,若 Leader 直接按多数提交,会在领导者频繁更迭时踩雷。论文 Figure 8 给出的反例轮廓如下。
口头推演(5 节点):
- (a) S1 在 term 2 于 index 2 写入条目,只复制到 S2,未达多数即宕机。
- (b) S5 当选 term 3,在 index 2 写入另一条(term 3),仅本地存在后宕机。
- (c) 错误规则:S1 恢复并当选 term 4,把 index 2 的 term-2 条目复制到多数后立刻 commit。之后 S5 仍可能凭「更新的 term-3 日志」当选,并用 term-3 条目覆盖 index 2——已 commit 的命令被抹掉。
- (d) 正确规则:Leader 只对当前 term 的条目用多数派推进 commit;旧 term 条目通过「更高 index 的当前 term 条目先 commit」被间接提交。生产里常见做法是新 Leader 立刻追加一条 no-op(空命令)以尽快完成间接提交。
手填日志表(建议先遮住答案自己填)
单元格写
index/term;空白表示该位置尚无条目。命令语义:A
= term-2 在 index 2 的业务写;B = term-3 在同一
index 的另一条写;N = term-4 的 no-op。
(a) S1 宕机前(只复制到 S1、S2)
| 节点 | idx1 | idx2 | idx3 | 备注 |
|---|---|---|---|---|
| S1 | 1/1 | 2/2 (A) | — | Leader,随即宕机 |
| S2 | 1/1 | 2/2 (A) | — | 已收到 A |
| S3 | 1/1 | — | — | |
| S4 | 1/1 | — | — | |
| S5 | 1/1 | — | — |
(b) S5 写完 2/3 后宕机
| 节点 | idx1 | idx2 | idx3 | 备注 |
|---|---|---|---|---|
| S1 | 1/1 | 2/2 (A) | — | 仍 down |
| S2 | 1/1 | 2/2 (A) | — | |
| S3 | 1/1 | — | — | 投过 S5 |
| S4 | 1/1 | — | — | 投过 S5 |
| S5 | 1/1 | 2/3 (B) | — | term-3 Leader,随即宕机 |
(c) 错误:S1(term 4)把 2/2 复制到多数并立刻 commit
| 节点 | idx1 | idx2 | idx3 | 备注 |
|---|---|---|---|---|
| S1 | 1/1 | 2/2 (A) | — | Leader;误把 idx2 标为 committed |
| S2 | 1/1 | 2/2 (A) | — | |
| S3 | 1/1 | 2/2 (A) | — | 刚被补齐,与 S1、S2 组成多数 |
| S4 | 1/1 | — | — | |
| S5 | 1/1 | 2/3 (B) | — | 稍后若当选,用 B 覆盖 A |
此时客户端若已收到「A 成功」,随后 S5 以更「新」的 lastLogTerm=3 赢得选举并覆盖 idx2,已提交的 A 消失——Safety 破裂。
(d) 正确:先追加并 commit 当前 term 条目 3/4 (N)
| 节点 | idx1 | idx2 | idx3 | 备注 |
|---|---|---|---|---|
| S1 | 1/1 | 2/2 (A) | 3/4 (N) | 只对 3/4 计多数 commit |
| S2 | 1/1 | 2/2 (A) | 3/4 (N) | |
| S3 | 1/1 | 2/2 (A) | 3/4 (N) | 三份 3/4 → commit;A 间接提交 |
| S4 | 1/1 | (对齐中) | (对齐中) | 随后追上 Leader |
| S5 | 1/1 | 2/3 (B) | — | 缺 3/4,对持有 3/4 的投票者而言更旧,无法再当选 |
自学检查:遮住上表,只看 §4.3 开头四句,能否在纸上复原 (a)→(d) 每一格?复原不了说明 Figure 8 还没进长期记忆。
这条规则与选举限制一起,堵住「先宣称提交、再被覆盖」的漏洞。
4.4 commitIndex
与应用
- Leader 的
commitIndex单调不减;通过AppendEntries传播。 - Follower 不得应用超过本地已知
commitIndex的条目。 - 状态机应用必须按 index 顺序、且仅应用于已提交前缀——否则同一 key 的可见历史会分叉。
4.5 客户端重试与「至少一次」
网络超时后客户端重试同一写,Leader 可能已提交也可能未提交。Raft 本身保证的是日志位置上的命令一致,不自动提供「恰好一次业务语义」。常见工程配套:
- 客户端为写生成唯一
request_id,状态机做成幂等; - 或在状态机层做去重表。
若不做幂等,用户会把「Raft 很安全」误读成「业务不会双花」——前者是共识安全,后者是应用协议。
4.6 只读路径为何另开专题
未经协助的 Follower 本地读,可能读到:
- 尚未提交的条目(若错误地暴露了未提交前缀);或
- 已提交但落后于 Leader 的前缀(线性一致性读失败)。
因此「读」要么走 Leader,要么用 ReadIndex / 租约读等协议扩展。本篇不展开实现,见 raft-deep。
五、安全不变量:为什么「读到的已提交数据」不会收回
本节给出四条性质与 Leader Completeness 的证明轮廓(非正式、无完整 TLA+)。形式化兴趣可参阅 Ongaro 论文附录与社区 TLA+ 规格。
5.1 四条性质
| 性质 | 含义 | 若破坏 |
|---|---|---|
| Log Matching | 同 index+term ⇒ 前缀全同 | 复制修复无法收敛 |
| Election Restriction | 选票只给日志足够新的候选人 | 落后节点当选并覆盖历史 |
| Leader Completeness | 已 commit 一条目必出现在所有更高 term 的 Leader 日志中 | 新 Leader 丢失旧提交 |
| State Machine Safety | 任意节点在 index \(i\) 应用的命令唯一 | 客户端看到矛盾状态 |
目标性质通常是 State Machine Safety;Raft 证明路线是先证 Leader Completeness,再由日志覆盖规则推出状态机一致。
5.2 Leader Completeness 证明轮廓(反证)
命题:若条目 \(e\) 在 term \(T\) 被 commit,则一切 term \(U>T\) 的 Leader 日志都包含 \(e\)。
假设存在反例,取最小的违规 term \(U\),其 Leader \(L_U\) 不含 \(e\)。
- \(L_U\) 当选时就不含 \(e\)(Leader 只追加不删自有前缀)。
- \(e\) 在 \(T\) 被 commit ⇒ 存在多数派 \(Q_c\) 已存 \(e\);\(L_U\) 当选需要多数派 \(Q_v\)。存在节点 \(v \in Q_c \cap Q_v\)。
- \(v\) 在投票给 \(L_U\) 前持有 \(e\)(中间各 Leader 若都含 \(e\),不会指示删除 \(e\))。
- 选举限制要求 \(L_U\) 的日志至少与 \(v\) 一样新。结合 Log Matching,可推出 \(L_U\) 也应含 \(e\)——矛盾。
故命题成立。Figure 8 的 commit 规则保证:被计入 commit 的「当前 term 条目」不会在上述推理里被后任合法覆盖。
5.3 从 Leader Completeness 到状态机安全
一条短链即可收束:
- 条目 \(e\) 在某 term 提交 ⇒ 一切后续 Leader 日志含 \(e\)(Leader Completeness)。
- Leader 日志为权威;Follower 经一致性检查与冲突截断,最终与 Leader 对齐(Log Matching)。
- 各节点仅按
commitIndex顺序应用 ⇒ 同一 index 的应用命令唯一(State Machine Safety)。
Figure 8 的规则保证第 1 步的前提「\(e\) 真的算提交」不会被后任合法否定。
5.4 Safety 优先于 Liveness
永久多数派丢失(例如五节点剩两节点且无法组成多数)时,集群应停止提交新命令,而不是在少数派上冒充成功。这不是实现疏忽,而是 Safety 优先的后果。运维上的「强制提升少数派」属于危险干预,需人工接受可能的数据分叉——共识库默认不会替你做这个决定。
另一侧是分区愈合:旧 Leader 若仍以为自己是 Leader 并接受客户端写,这些写在少数派上不得被当成已提交;一旦它连上持有更高 term 的节点,必须下台并回滚其未提交后缀。客户端若在超时后重试到新 Leader,应依赖 §4.5 的幂等设计。
六、快照与成员变更(最小必要集)
6.1 快照(Snapshot)
日志不能无限增长。节点将状态机打成快照,记录
lastIncludedIndex /
lastIncludedTerm,然后丢弃该 index
之前的日志。落后 Follower 可通过
InstallSnapshot(或等价流式传输)追上,而不必回放全部历史。
细节(分块传输、与 Apply 的并发、etcd 的 HTTP/gRPC 路径)见 raft-deep。
6.2 联合共识(Joint Consensus)
成员变更若「先加后删」或「先删后加」步骤不当,可能出现两个不相交多数派同时以为自己合法。论文方案是 Joint Consensus:先进入同时满足旧配置 \(C_{\mathrm{old}}\) 与新配置 \(C_{\mathrm{new}}\) 多数派的过渡配置,再切换到纯 \(C_{\mathrm{new}}\)。
flowchart LR
Cold[Config_Cold]
Joint[Joint_Cold_and_Cnew]
Cnew[Config_Cnew]
Cold --> Joint --> Cnew
单步成员变更与 etcd ConfChange V2 的工程简化见 deep 文;本篇只要求记住:变更期间的多数派定义本身也必须通过日志提交,不能「改完配置文件就重启当完成」。
七、工程补丁地图(只指路,不拆源码)
论文 18 页与生产库之间的差额,多半不是「改算法」而是补全故障与性能场景。下表一眼定位,细节全部链到深度篇。
| 主题 | 动机(一句话) | 阅读 |
|---|---|---|
| PreVote | 分区节点恢复时避免搅乱健康集群选举 | raft-deep §2 |
| ReadIndex / LeaseRead | 线性一致读少走完整日志往返 | deep §5 |
| LeaderTransfer | 主动移交领导权做运维 | deep §5 |
| ConfChange V2 / Learner | 多节点变更与冷启动副本不拖累 quorum | deep §5 |
| Pipeline / Batch / Async Apply | 抬升稳态吞吐 | deep §6 |
| Multi-Raft | 用多组 Raft 摊开 Leader 热点 | deep §7;本文 §8.3 |
旧的精简入门版仍保留在 Raft:让共识算法不再是黑魔法;需要 etcd 库结构时优先读 deep,而不是更早的 raft-etcd 单篇。
八、研究讨论:Raft 之后还在吵什么
本节按 WRITING_GUIDE 第十四节组织:谱系 → 分叉 → 假设与工程间隙 → 开放问题。不宣告「已被某路线统一」。
8.1 奠基谱系
| 工作 | 出处 | 在本文中的角色 |
|---|---|---|
| Fischer et al., FLP | JACM 1985 | Safety/Liveness 不可能同时在纯异步下兼得 |
| Lamport, Paxos Made Simple | 2001 | 单命令共识叙述;Multi-Paxos 规范不完整是 Raft 动机之一 |
| Ongaro & Ousterhout, Raft | USENIX ATC 2014 | 可理解性优先的完整复制日志协议 |
| Ongaro, PhD thesis | Stanford 2014 | PreVote 等扩展的原始出处 |
| Howard et al., Flexible Paxos | OPODIS 2016 | 选举/复制仲裁只需相交,不必同为多数 |
Ongaro 在论文中做了受控的学生理解实验:同等测试下 Raft 得分与自评理解高于 Paxos——这支撑的是可理解性主张,不是「Raft 渐近更快」。
8.2 Flexible Quorum:稳态延迟 vs 选举代价
Flexible Paxos 指出:准备阶段(选举/Prepare)与接受阶段(复制/Accept)的仲裁集只需两两相交,不必都取 \(|\mathit{majority}|\)。例子:\(N=5\) 时可取选举仲裁 \(4\)、提交仲裁 \(2\),使稳态写只等更小集合,但选举更难达成。
交集推演:\(N=5\),选举 \(4\),提交 \(2\)
记节点全集 \(S=\{S_1,\ldots,S_5\}\)。经典 Raft 取
\[ |Q_{\mathrm{elec}}| = |Q_{\mathrm{commit}}| = 3 \]
任意两个大小为 3 的子集必交,故「存过已提交条目的多数派」与「选出新 Leader 的多数派」共享至少一名见证者。
Flexible 配置改为:
\[ |Q_{\mathrm{commit}}| = 2,\qquad |Q_{\mathrm{elec}}| = 4 \]
(一般规则:只要任意合法 \(Q_{\mathrm{commit}}\) 与任意合法 \(Q_{\mathrm{elec}}\) 相交即可;一组充分条件是 \(|Q_{\mathrm{commit}}| + |Q_{\mathrm{elec}}| > N\)。)
验证本例:\(2+4=6>5\)。任取提交仲裁 \(C\)(\(|C|=2\))与选举仲裁 \(E\)(\(|E|=4\)):若 \(C\cap E=\emptyset\),则 \(|C\cup E|=6>|S|\),不可能。故交集非空。
Safety 直觉不变:条目 \(e\) 在 \(C\) 上持久化并按协议计为提交后,任何新 Leader 的选举集合 \(E\) 里至少有一名成员见过 \(e\)(再叠加入选限制与「只 commit 当前 term」等规则,完整表述见 Flexible Paxos 原文)。改的是代价:
| 经典多数 | 本例柔性配置 | |
|---|---|---|
| 稳态写 | 等 3 个应答 | 可只等 2 个 → 常绕过最慢副本 |
| 选主 | 凑齐 3 票 | 必须凑齐 4 票 → 容错与可用性变差 |
| 可容忍同时失联 | 2 节点(写与选都按 3) | 写路径仍可在「2 个活着」时前进;选主时失联 2 个就可能凑不齐 4 |
因此柔性仲裁不是「免费加速」,而是把延迟从高频路径(写)挪到低频路径(选举)。成员变更时 \(N\) 与两套 quorum 必须一起重算,否则「以为相交」会在换人后失效——这是工程间隙的核心。
工程间隙:经典 Raft 实现与教材几乎都钉死多数派,心智模型简单;柔性仲裁要重写投票与提交计数、仔细处理成员变更,调试成本上升。
FlexiRaft(Yadav & Rahut, CIDR 2023,Meta 生产 MySQL 复制栈)把柔性仲裁相关思路落到大规模 MySQL 部署动机中:跨地域副本下,固定多数提交会把写延迟绑在慢区 RTT;文中把 data commit quorum 做成可配置,并由其自动推导 leader election quorum 以保持相交。读该文时应分清:哪些是对 Raft 的协议参数化,哪些是 MySQL / binlog server / 地域 group 的集成细节(B 级系统经验,不是新的通用定理)。
FDRaft 一类工作用延迟感知动态缩小 commit quorum(学位论文路径常见);引用其吞吐数字时必须标明实验环境与是否经 peer review,本文不转述未核对的倍率。
8.3 Multi-Raft:横向扩展而非改单组语义
单组 Raft 的 Leader 在 CPU、网卡与磁盘上形成热点。TiKV、CockroachDB 等采用 Multi-Raft:数据按区间(Region / Range)切开,每段一个独立 Raft 组,领导权散落在不同节点。
边界:Multi-Raft 解决的是组内一致复制;跨区间原子事务需要上层协议(例如 Percolator 风格),不是「把 Raft 再调一次参数」就能消失。组内机制预修可先读 RocksDB 内核系列 与 raft-deep §7;HTAP / TiKV 分布式事务另题。
摊开 Leader 之后,运维重心往往变成:热点区间调度、Leader 分布是否均匀、迁移/分裂时的短暂不可写。单组 Raft 的「选主」问题,在 Multi-Raft 里变成「成千上万个小组的调度与抖动」——扩展了吞吐,也扩展了控制面复杂度。
8.4 并行提交路线:EPaxos 与连续日志之争
Raft 日志无空洞:index \(k+1\) 的提交依赖对前缀顺序的共识进展。Egalitarian Paxos(Moraru et al., SOSP 2013)允许无冲突命令并发提交,潜在降低多主延迟,但冲突处理与实现复杂度明显高于 Raft。
这是有文献的真实权衡,不是口号:
| 立场 | 主张 | 代价 |
|---|---|---|
| Raft 派 | 规范完整、工业实现成熟、易推断故障 | Leader 瓶颈;槽位串行 |
| EPaxos / 并行派 | 多主、冲突少时延迟更低 | 依赖图、恢复路径难 |
8.5 与「可理解性」目标的张力
Raft 论文把可理解性做成可测主张。十年后工业补丁(PreVote、流水线、异步 Apply、柔性仲裁)重新堆回复杂度。这不是否定论文,而是提醒阅读时分层:
| 层 | 该掌握什么 | 不该指望一次读完 |
|---|---|---|
| 论文核心 | 三角色、两 RPC、四不变量、Figure 8 | 性能数字 |
| 生产库 | I/O 解耦、流控、运维 API | 当作另一套共识数学 |
| 研究变体 | 改了哪条仲裁/日志假设 | 默认「已取代 Raft」 |
写配置中心、元数据服务:先保证论文核心实现正确。写跨洋多活存储:再阅读 Flexible / Multi-Raft / 上层事务,并单独做故障演练。
8.6 开放问题(截至 2026)
- 地理分布:跨区多数派写延迟是否主要通过柔性仲裁 / 层级复制消解,还是业务应接受「本区多写、跨区异步」的一致性降级?
- Leadership 负载:Multi-Raft 摊领导权后,调度、热点 Region、迁移是否变成新的运维核心?
- 成员变更可用性:联合共识正确,但频繁换人时可用性窗口如何量化?自动运维是否引入新的裂脑风险?
- 形式化与实现差:TLA+ / 机器证明覆盖论文核心后,PreVote、Pipeline、异步 Apply 组合是否仍有逃逸的边例?
- 与 BFT 的边界:互联网失控环境要不要上 HotStuff 类 BFT(见 16-hotstuff)?CFT Raft 的威胁模型是否被误用于敌对节点场景?
九、常见误解
- 「Raft 就是更好的 Paxos。」 Raft 在可理解性与协议完整度上针对工程落地;理论能力与 Multi-Paxos 同属 CFT 复制日志。速度数字取决于实现与负载,不能从论文标题推出。
- 「多数副本落盘 = 可以提交任意任期的条目。」 Figure 8 表明对旧任期条目直接按多数提交会破坏 Safety。
- 「选举超时越短越好。」 过短在抖动网络下触发无效选举,吞吐崩盘;须覆盖心跳 RTT 分布。
- 「Follower 可以本地服务任意线性一致读。」 默认不行;需要 ReadIndex / 租约等机制(deep 文),否则可能读到未提交或过时前缀。
- 「上了 Multi-Raft 就等于解决了分布式事务。」 只解决分片内复制;跨分片原子性另议。
十、三句话小结与阅读路径
- Raft 用强领导者 + 复制日志把共识拆成选举、复制与可证明的 Safety 规则;随机超时是活性的工程近似,不是数学上取消 FLP。
- 最难的一节往往是 Figure 8 的 commit 规则 与 Leader Completeness:多数派交集 + 选举限制 + 「只 commit 当前 term」共同守住已提交历史。
- 近年争论集中在柔性仲裁、地理延迟、Multi-Raft 扩展与并行提交替代;选型时先固定威胁模型与一致性需求,再读实现补丁,而不是先抄参数。
推荐路径
- 本篇(机制与不变量)
- raft-deep(etcd / Multi-Raft 工程)
- 14-epaxos → 17-consensus-engineering(替代路线与权衡)
- 下方五步可视化练习(必做)
对照 raft.github.io 的五步练习
打开 The Secret Lives of Data — Raft(官方可视化)。每一步先自己预测屏幕上会发生什么,再点播放;对不上就回到本文对应小节。
| 步 | 操作 | 你应能指出 | 对读本文 |
|---|---|---|---|
| 1 | 停掉当前 Leader,观察选举 | 谁先超时?term 是否加一?多数票如何凑齐? | §3.2–§3.3 |
| 2 | 故意让两名 Follower 超时接近(多次重开直到 split vote) | 为何本轮无 Leader?下一轮随机超时如何打破对称? | §3.1 |
| 3 | 向 Leader 提交一条写,观察日志格子 | 条目先出现在 Leader 哪一格?何时变「已提交」颜色/标记? | §4.1 |
| 4 | 在复制未达多数前杀掉 Leader,再选主 | 未提交后缀是否被新 Leader 覆盖或抛弃?客户端若已超时该怎么办? | §4.3、§4.5、§5.4 |
| 5 | 造出「旧 term 条目已在部分节点、新 Leader 上任」局面(多次故障) | 新 Leader 是否先写入当前 term 条目(可视化中的空/心跳推进)再间接提交旧条目? | §4.3 手填表 (d) |
做完五步仍觉得 Figure 8 飘忽:回到 §4.3,用纸笔只依据规则重填 (a)→(d),不要看答案表。
参考资料
核心论文
- Ongaro, Diego & Ousterhout, John. In Search of an Understandable Consensus Algorithm. USENIX ATC 2014.
- Ongaro, Diego. Consensus: Bridging Theory and Practice. Stanford PhD Dissertation, 2014.
- Howard, Heidi; Malkhi, Dahlia; Spiegelman, Alexander. Flexible Paxos: Quorum Intersection Revisited. OPODIS 2016.
- Moraru, Iulian; Andersen, David G.; Kaminsky, Michael. There Is More Consensus in Egalitarian Parliaments. SOSP 2013(EPaxos)。
- Yadav, Ritwik & Rahut, Anirban. FlexiRaft: Flexible Quorums with Raft. CIDR 2023(系统经验文,B 级辅助)。
- Fischer, Michael J.; Lynch, Nancy A.; Paterson, Michael S. Impossibility of Distributed Consensus with One Faulty Process. JACM 1985.
- Lamport, Leslie. Paxos Made Simple. 2001.
规范 / 实现 / 站内
- raft.github.io — 论文与可视化入口。
- Raft 深度重写:论文到 etcd。
- 共识工程权衡。
- 精简入门版。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【分布式系统百科】Raft 深度重写:从论文的 18 页到 etcd 的 15000 行
Raft 论文 18 页就能读完,但 etcd/raft 用了 15000 行 Go 才把它变成能在生产环境跑的代码。这篇文章从论文的每一个核心机制出发,逐一拆解工程实现中论文没说的东西:PreVote、ReadIndex、LeaderTransfer、ConfChange V2、流水线复制、Async Apply,以及 TiKV 的 Multi-Raft 实践。最后做一次精确的 Paxos 对比,并坦诚讨论 Raft 的已知缺陷。
Raft:让共识算法不再是黑魔法
Paxos 被引用了几千次,能正确实现它的人不超过几十个。Raft 用可理解性换工程落地,它的 Leader Election、Log Replication 和 Safety 三板斧,撑起了 etcd、TiKV 和大半个云原生基础设施。
【分布式系统实战】Raft 实现拆解:etcd 的共识算法到底长什么样
Raft 论文 18 页,etcd raft 库 ~15000 行 Go。中间的差距不是代码量,是论文没提的工程 edge case:PreVote、流水线复制、ReadIndex、joint consensus。
【分布式系统百科】共识问题的精确定义:Agreement、Validity、Termination
共识到底在解决什么问题?Agreement、Validity、Termination 三个性质的精确含义是什么?Safety 和 Liveness 的区分为什么如此关键?FLP 不可能定理对工程实践意味着什么?本文从形式化定义出发,逐步展开共识的变体、原子广播的等价性,以及状态机复制这个最重要的应用。