DDIA 导读(八):分布式系统的麻烦 📅 发布时间:2026/9/13 10:43:36 👁 浏览次数: 本文是《Designing Data-Intensive Applications》DDIA中文译名《数据密集型应用系统设计》第 8 章的导读。DDIA 是 Martin Kleppmann 所著的分布式系统经典本系列逐章导读把书的核心概念讲清楚。一句话主旨分布式系统与单机系统的根本区别不是节点多而是部分失效——单机要么全好要么全挂分布式系统却会有一部分节点活着、一部分死了而且你常常分不清死了和慢了。本章把分布式系统的三大不可靠——网络、时钟、进程暂停——逐一拆开告诉你为什么在分布式系统里连判断一个节点是否挂了都没有可靠答案以及为什么你写出的分布式代码几乎一定有 bug。核心概念拆解1. 部分失效——分布式系统的根本困难单机系统是个全有全无的世界要么正常工作要么整机崩溃——电源拔了CPU 不转了所有组件同时停。但分布式系统不一样一个节点挂了其他节点还在跑一条网线断了另一个机房还在服务。分布式: 部分失效系统仍在跑但行为不确定节点A 正常节点B 挂了节点C 正常节点D 慢了(是死是慢?)单机: 全有全无故障重启正常整机崩溃所有组件同时停部分失效之所以可怕不是有节点挂了而是你不知道哪些挂了。网络不可靠你发一个请求没回应——对方是死了还是网络分区了还是只是慢了你无从区分。单机世界里超时是个明确信号进程没了分布式世界里超时只是我等不及了——对方可能还活着甚至已经把请求处理完了只是响应还没传回来。问题→方案问题——分布式系统里部分节点失效是常态且你无法可靠区分失效“慢”“网络分区”。场景——任何跨节点调用都可能超时超时后你不知道对方做了什么。方案——不要信任超时作为确定性信号超时只代表我决定不再等了不代表对方没做。所有设计都要假设请求可能已到达但响应丢失因此操作必须幂等重试不会产生副作用否则重试会导致重复执行。2. 不可靠网络——超时不是确定性信号大多数分布式系统跑在 IP 网络上采用异步网络模型网络只管尽力交付不保证——不保证消息到达、不保证顺序、不保证延迟上界。消息可能丢失网络拥塞、路由器丢包可能乱序可能延迟任意久。节点客户端节点客户端网络不保证: 不丢、有序、有延迟上界超时: 我不再等了但 N 可能已处理完请求!只是响应丢了请求(可能到达,可能没到)??? (没回应)重试(必须幂等!)超时的两难超时设太短 → 正常请求被误判失败重试导致重复处理需要幂等超时设太长 → 真挂了也要干等拖慢整个系统DDIA 的建议不要用固定的超时而是用动态调整的超时——根据历史延迟分布如 φ 延迟预测自适应设置快的时候快超时慢的时候多等等。但这仍不能消除不确定性——超时永远只是猜测不是判决。网络分区网络故障把系统切成互不可达的两组甚至更多组每组内部正常通信组间完全断联。分区不罕见——网络抖动、交换机故障、配置错误都可能触发。分区的难点是被隔离的组不知道自己是被分区的少数派还是完整的多数派——它只能看到我这边能通信无法得知对方还在不在。这也是 CAP 定理的物理根源下一章细讲。3. 不可靠时钟——墙上钟会撒谎分布式系统常用时钟来做两件事给事件排序谁先谁后和测量时长超时、租约。但分布式系统的时钟有两类混用会出事单调钟 Monotonic只增不减不受 NTP 影响适合: 测量时长(超时)不适合: 跨节点比较墙上钟 Time-of-day从 NTP 同步可回拨/跳变适合: 给事件打时间戳不适合: 测量时长墙上钟Time-of-day clock返回人类时间如 14:32:05通常从 NTP 同步。问题是它可能回拨或跳变——NTP 检测到时钟偏快会调慢甚至回拨管理员手动调时间会跳变。用墙上钟测量时长end - start可能得到负数或零。单调钟Monotonic clock只保证单调递增适合测量时长超时计时但语义上不适合给事件排序——不同节点的单调钟起点不同无法跨节点比较。时钟漂移即使有 NTP不同节点的时钟也不是精确同步的——硬件晶振有误差典型每天漂移几毫秒到几百毫秒。你以为两个节点的时间戳只差 1ms实际可能差几秒。时钟不可信的后果——租约与 fencing token很多系统用租约lease来选主——主节点持有一个带过期时间的租约过期前它是主过期后让别人接管。但如果主节点的时钟慢了它可能以为自己租约还没过期继续以主身份写入而实际上新主已经接管——两个主同时写脑裂。DDIA 的解法是fencing token每次租约发放时附带一个单调递增的 token 号存储层只接受 token 号比上次大的写请求——即使旧主的时钟撒谎它带的 token 号更小存储层直接拒绝。新主(token34)存储层旧主(token33)新主(token34)存储层旧主(token33)时钟慢了, 以为租约没过期写入(token33)33 上次34, 拒绝!写入(token34)34 上次, 接受4. 进程暂停——GC 停顿比你想的常见即使网络和时钟都完美还有一关进程可能被暂停任意久。一个正在执行的节点可能因为垃圾回收GC停顿几十秒甚至几分钟在这期间它对外界消失了——不响应心跳、不处理请求——但从它自己的视角只是打了个盹醒来后继续执行完全不知道自己暂停了多久。ZooKeeper(协调)存储层主节点ZooKeeper(协调)存储层主节点持有租约, 过期前续约⏸ GC 停顿 60 秒新主已接管GC 结束, 醒来如果无 fencing token → 脑裂!租约过期, 选新主继续写入(以为还是主!)进程暂停的陷阱在于被暂停的节点醒来后不知道自己暂停过。它的内存状态完好代码从上次执行处继续它觉得自己一直是主。如果协调层如 ZooKeeper在暂停期间已经把主让给了别人这个僵尸主的写入就会和新主冲突——又是脑裂。这进一步说明 fencing token 的必要性光靠租约过期不够进程暂停让旧主感觉租约还没过期还需要存储层用 fencing token 做最后的防线——不管旧主怎么以为token 号比新主小就拒绝。DDIA 反复强调不要假设进程不会暂停所有依赖我知道自己是唯一主的逻辑都可能是错的。5. 拜占庭故障与 FLP 不可能定理——信任的边界本章末尾点到两个更硬的结论拜占庭故障Byzantine Faults前面讨论的所有故障都假设节点是诚实但可能失效的——它要么正常工作要么挂了不会撒谎不会故意发错误数据。但如果有节点故意作恶或发错误数据被黑、硬件错误导致数据损坏那就是拜占庭故障。本章的共识协议不处理拜占庭故障——它们假设节点是诚实的。处理拜占庭故障需要 BFT拜占庭容错协议开销大得多本书不深入。本书不讨论拜占庭场景因为大多数数据中心系统假设节点是受信任的——你可以控制硬件、运维、网络。拜占庭容错主要用在区块链、跨组织协作等不信任场景。FLP 不可能定理Fischer、Lynch、Paterson 在 1985 年证明——在异步网络延迟无上界中只要有一个节点可能崩溃就不存在一个一定能终止的确定性共识算法。换句话说理论上异步网络的共识不可能做到既正确又一定能达成。这听起来像分布式系统没救了但实践中有出路——FLP 是最坏情况下可能不终止而真实网络不会永远最坏实际共识协议Raft/Paxos通过引入随机性或超时来打破 FLP 的理论障碍在绝大多数情况下能终止。关键取舍本章没有方案选择——它是一章警告告诉你分布式系统的物理现实是什么。但有一个核心取舍贯穿全书取舍选择 A选择 B代价信任超时超时即判死快速故障转移超时只是不等了谨慎处理残留请求A 快但可能误杀重复处理B 稳但复杂信任时钟用墙上钟排序、租约过期用 fencing token 做最后防线光靠时钟会脑裂加 token 多一层但安全假设不暂停进程一直跑心跳准假设随时可能暂停所有我是主的判断都可疑前者简单但不可靠后者需要 fencing一句话总结这章在分布式系统里网络不可靠、时钟不准、进程会暂停——这三件事让判断真相变得极其困难。任何基于我知道对方状态或我知道当前时间的假设都可能是错的。解法的方向不是消除不确定性做不到而是设计成在不确定下仍安全幂等操作容忍重试、fencing token 容忍脑裂、共识协议容忍节点失效。这些解法正是下一章一致性与共识的主题。下一篇第 9 章——一致性与共识。全书的钉子户线性一致性、Raft/Paxos、CAP。