10 · 分布式系统的硬道理:部分失败、时间与共识
一句话点题:单机悄悄给了你三个奢侈:操作要么成要么败、全世界有一个统一的现在、函数调用必达。一旦跨过两台机器,这三样全没了。 入门篇的大白话 CAP 只是门口,本章往下挖到真正硌脚的硬骨头:分布式到底难在哪;以及在 AI 帮你写实现的时代,哪些判断依然只能由你来下。
🧭 进阶篇从这里开始。 入门篇 01–09 教你看懂一个系统,并能从 0 设计一个中小系统;从本章起的进阶篇,处理的是另一类问题:系统一旦做大、做关键,才会露出獠牙的那些硬骨头,比如分布式、失败、规模、演进和组织。
先别紧张,这一章是入门篇的老朋友变深,不是另起炉灶。还记得 05 · 数据与状态 里点赞数可以最终一致、ATM 余额必须强一致的例子吗?还记得大白话版 CAP 那句:网络一断,一致性和可用性只能二选一吗?这一章干的事,就是把这些你已经熟悉的结论翻过来,看它们背后到底踩了什么坑:为什么会最终一致、为什么强一致那么贵、网络到底是怎么断的。你已经站在门口了,我们只是往里再走一层。
这恰恰是 AI 时代最值钱的能力。AI 几秒就能吐出一份能跑的 Raft 实现,但这里到底要不要共识、能容忍多大不一致、网络裂开那一刻你要正确还是要在线,这些判断题的代价由你的业务承担。AI 给不了你标准答案,因为答案取决于你愿意拿什么换什么。实现越来越廉价,判断越来越值钱。 这条主线会贯穿整个进阶篇。
一、单机的三个奢侈,跨过两台机器就全没了
写单机程序时,有三件事你习以为常,从没意识到它们是奢侈品:
单机世界:三个奢侈 分布式世界:三个全没了
────────────────── ──────────────────────────
① 函数调用必达 ① 网络不可靠:丢包、乱序、重复、延迟、分区
② 全世界一个现在,同一时钟 ② 没有全局时钟:每台机器的表都不一样,所谓同时是幻觉
③ 要么成功、要么失败 ③ 部分失败:有的节点成了、有的败了、有的不知道死活前两条都好理解,第三条部分失败(partial failure)才是万恶之源。单机里一个操作的结局是二元的:成,或败。分布式里多了一个要命的第三态:不知道。
你给另一台机器发了个请求,等了 2 秒没回音。它是没收到?收到了但还在算?算完了但回包丢了?还是整台机器已经宕了?你无法区分它死了和它只是慢。这叫灰色失败(gray failure)。
你手里唯一的武器是超时(timeout):等够一段时间还没回,就当它失败。但超时本质是一次猜测。猜短了,会把只是慢的节点误判为死,然后重试,雪上加霜;猜长了,故障恢复就迟钝。整个分布式系统的可靠性设计,很大程度上就是在跟这个分不清死与慢的问题搏斗。
架构上要反过来想:单机时代你写代码默认调用会成功;分布式时代要默认每一次跨网络调用都可能失败、超时,甚至重复送达。然后问自己:这种情况下,我的系统会怎样?这一个思维翻转,是入门和进阶的分水岭。
二、一致性不是一个开关,是一道光谱
05 · 数据与状态 里我们把一致性拉成了强一致到最终一致的一条线。现在放大中间地带,它其实有好几档,每一档要付的账都是延迟与可用性:
贵 ◀───────────────────────────────────────────────▶ 便宜
线性一致 顺序一致 因果一致 最终一致
(Linearizable) (Sequential) (Causal) (Eventual)
全网立刻看到 大家看到的 有因果的事 迟早一致,
同一个最新值, 顺序一致, 顺序不乱,无关 中间窗口各看各的
像只有一台机器 但可能不是实时 的爱咋咋地
↑ ATM 取钱 ↑ 配置下发 ↑ 群聊消息/评论 ↑ 点赞数/浏览量- 线性一致:最强。任何人在任何节点读,都立刻读到最近一次写入的值,仿佛系统只有一台机器。代价最高,因为需要节点间协调,延迟和可用性都要为它让路。
- 因果一致:一个很实用的中间档。你回复了我的消息,这类有因果关系的操作对所有人顺序一致;两个陌生人各发各的,这类没有因果关系的并发操作谁先谁后无所谓。它比线性一致便宜得多,又能避免先看到回复、后看到原帖这种荒谬。
- 最终一致:最弱也最便宜。写完之后副本们慢慢追平,中间窗口允许各看各的。
关键认知:强一致不是更好,而是更贵。每往左升一档,你都在为它支付延迟和可用性的账单。架构判断不是尽量强,而是先问这份数据配得上多强:ATM 余额配得上线性一致;群聊消息配因果一致就够;点赞数用最终一致都嫌奢侈。把宝贵的强一致配额,花在真正会出事的地方。
三、CAP 只是开场白,PACELC 才是你天天在付的账
05 讲了 CAP 的人话版:网络分区(P)迟早发生,届时只能在一致性(C)和可用性(A)里二选一。但 CAP 漏了一大半真相:绝大多数时候网络是好的,这时你在付什么账?
补全它的是 PACELC:
if (P 网络分区) { // 偶尔发生
二选一: C 还是 A // 要正确,还是要在线?
} else { // ★ 99% 的时间在这里 ★
二选一: L 还是 C // 要低延迟,还是要强一致?
}- 分区时 PAC:网裂成两半,你要么拒绝服务保正确,也就是选 C;要么继续服务但可能给旧数据,也就是选 A。这是 CAP 讲的部分。
- 没分区时 ELC:就算网络一切正常,强一致依然要钱。为了让所有副本同意一个值,得等节点间往返协调,这就是延迟 L。你想更快、要低 L,就得放松一致性 C。
一句话翻译这两行公式:CAP 容易让你以为,只有网络分区这种极端时刻才需要权衡一致性;PACELC 提醒你,网络好端端的时候,也就是 99% 的日子里,你每写一次数据、每决定一次要不要等所有副本都确认,就已经在拿延迟换一致性了。这不是出事才付的意外险,而是你天天在付的水电费。
判断要点:一致性并不是只有网络故障时才需要权衡。即使岁月静好,你每一次决定要不要等所有副本确认,都是在拿延迟换一致性。这才是你天天在付的账。
四、没有现在:逻辑时钟买到的是因果正确
💧 深水区,初读可跳过,主线不依赖它。这一节是分布式里最学术的一块。你只需带走一句话:分布式里没有统一的现在,所以判断两件事谁先谁后,靠的不是看表,而是看谁导致了谁,也就是因果。至于下面那三种时钟的名字,绝大多数业务一辈子用不上。读不下去就跳到第五节,毫无损失。下面是给好奇的人讲透的版本。
既然每台机器的物理时钟都有偏差、网络延迟又不确定,那事件 A 比 B 先发生这个判断在分布式里怎么才算数?
先体会一下这个坑有多真实:你和朋友各看各的手机抢同一张票,两部手机的表可能差了几十毫秒;而谁先点的这件事,偏偏就发生在这几十毫秒里。靠对比两块表的读数来判断谁先谁后,是不可靠的,因为表本身就对不齐。
办法是放弃物理时间,改用因果关系:不问几点几分,只问这件事是不是因为那件事才发生的。这就是逻辑时钟:
节点甲: 事件a1 ──发消息m──▶
╲
节点乙: 事件b1 ────────────── ▶ 收到m ── 事件b2
↑
逻辑时钟保证:a1 发出消息,一定排在 b2 收到消息前面,
因为 b2 在因果上依赖 m,而 m 来自 a1。
至于 a1 和 b1 谁先?它们没有因果关系,算并发,不强行排序。关键招数其实很朴素:让每条消息都带上发送方此刻的计数,收到的人把自己的计数往后调到比它更大。这样发送一定排在接收前面,因果顺序就被钉死了。在这个朴素思想上,有三种做法,越往后越精细:
- Lamport 时钟:给所有事件排出一条总队,任何事件都能比较先后。
- 打个比方,它像微信群里按消息到达服务器的先后强行编号,让所有消息都有唯一序号。缺点是它分不清 B 是真的在回复 A,还是 A 和 B 只是恰好同时各发各的。它只保证不会先看到回复、后看到原帖,但会把其实互不相干的两件事也硬排出个先后。
- 向量时钟(Vector Clock):更进一步,能看出哪些事件其实是同时发生、互不相干的,也就能发现写冲突。
- 打个比方,两个人同时编辑同一份文档的同一行,向量时钟能识别出这两个改动谁也不知道对方,于是系统知道这里撞车了,得让人来决定留谁,而不是稀里糊涂用表上谁的时间晚覆盖掉另一个。
- 混合逻辑时钟(HLC):把物理表的时间和逻辑计数缝在一起,既贴近真实时间,又保证因果顺序。现代分布式数据库做某一刻的一致性快照常用它。
- 一句话:前两种纯逻辑,读数和真实时间脱节,不利于排查问题;HLC 让序号看起来还像个时间,兼顾两边。
判断要点:大多数业务系统用不上逻辑时钟,别为了显得高级而引入。但一旦你的系统要保证因果正确,它就绕不开:实时协同文档 里多人编辑的合并顺序、实时通讯 里消息的时序、分布式数据库的一致性快照……背后都是同一个问题:在没有全局钟的世界里,如何确定先后。
五、共识:最贵的一种协调,千万别滥用
有时候你就是需要一组机器对某件事达成铁板一块的一致:谁是主库?这条复制日志的第 100 条是什么?这把分布式锁现在归谁?这就是共识(Consensus)问题,Raft / Paxos 就是解它的。
共识买到的东西很硬核:即使部分节点宕机或失联,存活的多数派仍能对一个值、一条日志的顺序达成唯一且一致的决定。它是分布式世界里单一权威真相的来源。
打个生活里的比方:共识就像一群人开会表决,任何决议都得过半数举手才算通过。好处是稳,哪怕几个人临时离场,只要还凑得齐过半数,会议照样能拍板,而且不会出现两拨人各自通过相反决议的分裂。坏处也一样直白:每个决定都得等过半数的人来回确认一遍,人越多、坐得越远,比如跨机房,这一圈下来就越慢。所以共识适合开重要董事会,不适合随便拉个群聊。
但它非常贵:
每做一个决定,都要走一轮多数派投票:
提议者 ──▶ [节点1][节点2][节点3][节点4][节点5]
╲ │ │ ╱
等到 > 半数 确认,才算数(这一来一回 = 延迟)
• 至少 3 或 5 个节点(容忍 1~2 个挂) • 写吞吐受限于单一 leader
• 每次写多一轮网络往返(延迟涨) • 成员变更(加减节点)很微妙、易出错判断要点:本节最重要的一句是,共识很贵,只用在必须有唯一权威顺序的少数地方,比如选主、集群元数据、分布式锁、复制日志/状态机。绝大多数业务数据不该直接跑共识。一个把每条业务写入都塞进共识组的系统,是在为根本不需要的全局唯一顺序支付高昂的延迟与吞吐代价。
反模式是把 Raft 集群当通用数据库使。更稳的做法是让共识只管那一小撮全局只能有一个答案的元数据,业务数据该分片分片、该最终一致就最终一致。
六、Exactly-once 是个幻觉:把正确性交给幂等
新人最爱问:消息系统怎么保证恰好一次(exactly-once)投递?残酷真相是传递层做不到。因为网络会丢、会重,你发出一条消息后没收到 ack,只有两种选择:
重发(可能造成重复) ──▶ 至少一次 (at-least-once):不丢,但可能重
不重发(可能造成丢失)──▶ 至多一次 (at-most-once):不重,但可能丢
恰好一次作为传递保证 —— 不存在。真正能做到的,是换个地方解决:
at-least-once 投递允许重复,消费端幂等让重复只生效一次,最终得到业务效果上的恰好一次。
幂等的意思是:同一个操作执行一次和执行十次,结果一样。实现办法通常是给每个操作一个幂等键,消费端用一张去重表记下这个键处理过了,再来直接跳过。
判断要点:别在传输层追求虚幻的 exactly-once,把正确性下沉到消费端的幂等设计。支付系统 的幂等扣款、通知系统 的去重限频,都是这一招的活样板。
这条 at-least-once + 幂等是下一章的伏笔,它正是 Saga、Outbox、事件溯源这些工程手法的地基,目标都是在分布式里把数据弄对。
📌 真实案例:43 秒的网络分区,如何让 GitHub 乱了 24 小时
2018 年 10 月 21 日,GitHub 一次例行更换光纤设备,导致美东主数据中心与美东网络枢纽断联 43 秒——仅仅 43 秒。但这 43 秒精准踩中了本章的每一根硬骨头:
- 部分失败 + 失败检测:断联期间,美东数据中心继续接收了写入。它没死,只是和外界失联了;而管理数据库拓扑的、基于 Raft 共识的编排器 Orchestrator,把美东失联判定为美东主库不可用。这正是分不清死与慢的灰色失败。
- 共识的双刃:美西和公有云的 Orchestrator 节点凑齐了多数派,按 Raft 共识自动把主库切到了美西,让写入转向美西。一切都按配置正确执行。
- 没有全局真相,数据分叉了:43 秒后网络恢复,两岸各自都接收过写入,成了两个都自认为是主、且各持对方没有的数据的分叉集群。自动调和会有丢数据风险,于是只能人工小心修复,服务降级整整 24 小时 11 分钟。
事后 GitHub 的头号整改措施是:禁止 Orchestrator 跨区域提升主库。换句话说,一个为单节点故障设计的自动切换机制,在整个区域分区面前,自动做出了应用层根本扛不住的决定。
教训精确对应本章:部分失败让你分不清死与慢;没有全局时钟让失败检测必然有误判;共识能在多数派间达成一致,却也可能在分区时完全按规则把系统切进一个灾难性的拓扑。自动化越强,越要想清楚它在极端情况下会做出什么决定。
🤖 AI 时代,这章为什么不是过时的底层细节
你可能会想:这些不都是 AI 能帮我写的底层吗?恰恰相反——
- AI 写实现,你下判断。 AI 能生成 Raft、重试、幂等去重的代码;但这里要不要共识、容忍多大不一致、分区时选 C 还是 A,是判断题,代价由你的业务承担。会写 ≠ 会选。
- AI 原生系统本身就是重度分布式。 看看刚上线的 Agent 模板:AI Agent / 工作流平台 的长任务要可恢复来面对部分失败,工具调用要在 at-least-once 重试下保持幂等,记忆要在多步之间保持一致;Claude Code 这类编码 agent 的并发子代理、检查点恢复,也都站在这章的硬道理上。LLM 把不确定性又叠了一层在分布式的不确定性之上,硬道理只会更吃重,不会过时。
🎯 随堂检验
本章小结
- 核心论断:分布式难,难在单机的三个奢侈全没了:网络不可靠、没有全局时钟、部分失败。其中部分失败最致命,因为它带来分不清对方是死了还是只是慢的灰色失败,而你唯一的武器超时只是猜测。
- 一致性是一道有价目表的光谱:线性 → 顺序 → 因果 → 最终,每往强档升一级,延迟与可用性的账单就涨一截。判断不是尽量强,而是这份数据配得上多强。
- PACELC 比 CAP 完整:分区时在 C/A 间选;不分区的 99% 时间里还在 L/C 间选,强一致即使在岁月静好时也要花延迟买。
- 没有全局钟,就用因果:逻辑时钟(Lamport / 向量 / HLC)买的是因果正确。多数系统用不上,但协同、时序、快照绕不开。
- 共识最贵,别滥用:Raft/Paxos 给你单一权威真相,代价是多数派往返、节点下限、吞吐受限。只用在选主、元数据、锁、复制日志这类全局必须唯一的地方。
- Exactly-once 是幻觉:传递层只有 at-least-once 或 at-most-once;at-least-once + 消费端幂等 = 效果上的恰好一次。
- AI 时代主线:实现越来越廉价,选型与容错边界的判断越来越值钱;而 AI 原生系统本身就是重度分布式,这些硬道理只会更重要。
承上启下:这一章把分布式的病理摆了出来:为什么会乱、会丢、会分叉。下一章,也就是进阶篇第 2 章《数据一致性工程》,我们接着这章结尾的 at-least-once + 幂等往下走,讲在没有跨服务事务的世界里,怎么真的把数据弄对:Saga、Outbox、幂等去重、事件溯源与 CQRS 的落地。
💬 评论