Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

为什么写这本书

1 一个真实的小事故

一个内部工具平台提供「发券」能力:客户端调用 POST /tool/issue-coupon,服务端写数据库、调下游发券、返回券号。某天网络抖动,客户端在 3 秒时超时;客户端 SDK 的重试策略是「超时重试 3 次,指数退避」。3 次重试都超时,用户看到失败,点了第二次「发放」。最后用户收到 4 张券,财务对账时才发现。

复盘时三个团队各说各话:

  • 客户端团队说:「超时就是失败,失败当然要重试。」
  • 服务端团队说:「请求都处理了,是网络没把响应带回去。」
  • 下游团队说:「我们只保证至少一次,重复调用要上游去重。」

三方都没有说错,但三方合起来造出了一个重复副作用。这本书的第一章就从这件事开始,因为它是分布式系统最典型的形状:不是「机器坏了」,而是「成功与失败的边界在网络上变得不可判定」。

2 为什么现有的书不够用

市面上的分布式教材大致分三类。

第一类是协议教材:Paxos、Raft、2PC、线性一致性,证明写得很漂亮,但读者学完不知道 etcdctl get 的 --consistency=s 和 --consistency=l 差别在哪,也不知道为什么 Kubernetes 的 apiserver 会在 etcd 选举时抖动。

第二类是系统教材:讲 HDFS、Spark、Kafka、Dynamo 的架构,图很多,但代码离业务太远;读者能画出三层架构,却写不出一个带幂等键的接口。

第三类是工程手册:重试、熔断、限流、可观测性,条目很全,但缺少「为什么」——为什么指数退避要加抖动?为什么重试预算比重试次数更重要?为什么线性一致读在 Raft 里要走一次心跳?

这本书试图把三者接起来:协议给出保证,系统给出实现,工程给出边界。 每讲一个协议,都要回答「它在哪个失败模型下成立」;每讲一个工程手段,都要回答「它把哪一类失败转换成了哪一类失败」。

3 与四本母本的关系

本书的母本定位是四本书的组合,而不是任何一本的替代:

母本它给了什么本书怎么用不重复什么
谢希仁《计算机网络》第 8 版分层、IP、TCP、应用层协议的系统视角作为「请求离开本机之后」的底层事实来源;只在需要解释超时、连接复用、DNS 轮询时引用不重写 TCP 状态机、拥塞控制、HTTP 报文
张彦飞《深入理解 Linux 网络》Linux 协议栈的实现路径与性能视角解释「为什么客户端超时了服务端却收到了」「为什么连接池会放大故障」不重写收包/发包路径、epoll、连接队列
《Python 网络编程》socket、并发服务器、协议实现的工程视角两个项目的 HTTP 服务、超时、连接处理沿用其工程风格不复制代码,不把 API 手册抄成章节
《Linux 系统与内核》网络/云原生篇内核、容器、K8s 的完整基础只在边界处交叉引用,作为前置知识不重写 TCP/IP、socket、epoll、HTTP/DNS/TLS、容器与 K8s 基础

这张表说明一件事:本书的“新”不在底层协议,而在底层协议之上的失败处理与状态管理。 读者如果对 TCP 还不熟,应该先去补 Linux 书第 4 篇;如果已经熟,本书可以从第 1 章直接开始。

4 三个贯穿全书的问题

4.1 这次调用到底成功了吗

在单机上,函数返回就是成功,抛异常就是失败。跨进程之后,出现第三种状态:调用方不知道。请求可能没到、可能到了没执行、可能执行了没提交、可能提交了响应丢了。本书把「未知」当作一等状态,而不是把它硬塞进成功或失败。

第 1 章定义请求状态机,第 2 章把未知拆成超时与重试,第 12 章用一个可运行的服务证明:未知状态下,唯一安全的做法是让重试携带同一个幂等键,而不是让客户端「再发一次新请求」。

4.2 读到的值是最新的吗

复制带来延迟,延迟带来旧值。旧值不一定是错误——取决于你向读者承诺了什么。第 3 章讲复制拓扑,第 4 章把「线性一致、顺序一致、因果一致、最终一致」变成可判定的历史条件,第 13 章用三节点 KV 让你亲手制造陈旧读。

这里最容易被口号带偏的是 CAP。本书不会写「CAP 三者选二」就结束,而是给出:分区发生时,究竟是放弃线性一致还是放弃可用;PACELC 在无分区时又要求你在延迟与一致之间选什么;以及一个系统在什么条件下才算「满足线性一致」。

4.3 出故障时看什么

没有观测的分布式系统只能靠猜。第 10 章把 trace、指标、日志、回放拆成四种证据,并说明每种证据的盲区:trace 会漏采样,指标会丢标签,日志会重复,回放会放大副作用。第 11 章把观测接到容量与演练上:不是「看板好看」,而是「用一条命令判断是过载、是重试风暴,还是下游变慢」。

5 这本书的写法

  • 先失败,后机制:每一节尽量从一个可复现的失败或一条真实请求开始,再解释机制。没有失败场景的协议段落,会被压缩或删除。
  • 给边界,不给口号:不说「最终一致就够了」,而说「在什么业务下、允许多长时间的旧值、用什么读修复兜底」。
  • 可执行:命令能复制运行,代码能编译/运行,脚本 bash -n 通过;不能运行的片段明确标为伪代码。
  • 不复制:所有图自绘,所有代码自写;公开案例只引用事实、时间线和链接,不搬运原文。
  • 可检查:每章有出口检查,全书有覆盖矩阵和 check_book.py。写得好不好可以争论,覆盖没覆盖、锚点有没有断,是机械可查的。

6 写给谁

  • 写过 HTTP 服务、用过数据库,但一遇到「超时重试」「主从切换」「缓存不一致」就靠经验的后端工程师;
  • 正在把单体拆成服务、需要给团队定 RPC 与数据一致性规范的架构师;
  • 要做容量规划、故障演练、on-call 的 SRE;
  • 想从「会调 API」走到「能解释实现」的学生。

不要求你会写 Raft,但要求你能读 Python;不要求你部署过 Kubernetes,但要求你知道 Pod 是什么。如果这些都不熟,先读 Linux 书的对应篇章,再回来。

一句话

分布式系统的难点不是「把机器连起来」,而是「在部分失败、没有全局时钟、消息可能重复的条件下,仍然让上层看到一个可解释的状态」。

下一站:范围、取舍与不重复清单——先把本书不写什么划清楚,后面的每一章才不会变成其他书的复述。