Skip to content

分布式系统:在故障中维持共同状态 ​

一台机器上的函数调用通常有明确结果;跨网络调用可能已经执行,只是回复丢了。分布式系统的难处,从这个“无法确定”开始。增加副本可以提高容错能力,却又引入一个问题:当不同副本收到不同请求,它们如何对状态达成一致?

本篇沿 MIT 6.5840 的核心问题组织,课程入口采用 Spring 2026,算法细节以 Raft 扩展论文为准。重点是 RPC、复制状态机、Raft、KV、分片与事务。配套实现是独立的 Python 确定性模型,方便观察故障,不声称完成了 MIT 的 Go 实验。

一个贯穿全篇的请求 ​

客户端执行 add(counter, 1)。服务器 A 写入日志,向 B、C 复制,在多数副本确认后提交,执行状态机并回复。此时回复丢失,客户端重试;紧接着 A 与其他节点失联,B 成为新的 leader。正确系统应既不丢失已经成功的加一,也不因重试加两次。

sequenceDiagram
  participant C as 客户端
  participant L as Leader
  participant F as 多数派副本
  C->>L: 请求编号 17,加一
  L->>F: 复制日志
  F-->>L: 持久化确认
  L->>L: 提交并执行,记录请求结果
  L--xC: 回复丢失
  C->>L: 重试编号 17
  L-->>C: 返回已记录的结果
查看流程图文本
sequenceDiagram
  participant C as 客户端
  participant L as Leader
  participant F as 多数派副本
  C->>L: 请求编号 17,加一
  L->>F: 复制日志
  F-->>L: 持久化确认
  L->>L: 提交并执行,记录请求结果
  L--xC: 回复丢失
  C->>L: 重试编号 17
  L-->>C: 返回已记录的结果

这个流程的每一条箭头都可能延迟、丢失或重复。先写正常路径,再逐条删掉箭头,是设计测试的起点。

阅读顺序 ​

  1. RPC 与故障:超时、重试、幂等与 MapReduce。
  2. 一致性模型:系统向使用者承诺什么。
  3. Raft:如何选择 leader、复制和提交日志。
  4. 容错 KV:把日志变成客户端可用的服务。
  5. 分片与迁移:扩大容量又不失去数据归属。
  6. 跨分片事务:一致性与原子提交的区别。
  7. 运行故障实验:验证分区、重试和恢复。
  8. 自测与设计复盘。

先明确系统假设 ​

本手册主要讨论 crash-stop/crash-recovery 故障:节点可能停止、重启或失联,但不故意发送恶意矛盾信息。拜占庭容错需要另一组协议与假设。时间上采用异步或部分同步思考:延迟通常有限,但没有足够可靠的固定上界来区分“慢”和“死”。

安全性要求坏事永远不发生,例如同一日志位置不能提交两个不同命令;活性要求在合适条件下最终推进,例如多数节点互通且选举超时配置合理时能选出 leader。网络分区期间保住安全性,并不保证每个分区都能继续写入。

验收本篇时,应能回答三个问题:这次成功回复依赖哪些节点?失联节点恢复后如何赶上?客户端重试会不会重复产生副作用?