12 · 为失败而设计:韧性工程
一句话点题:高可用不是祈祷别出事,而是预设一定会出事。你控制不了组件什么时候坏,但你能决定它坏了之后系统会怎么反应。韧性工程,就是把这件事从运气,变成可以下注、可以验证的工程。
🧭 进阶篇第 3 章。 06 · 质量属性与取舍 给了你衡量可用性的标尺:几个 9 对应每年允许停机多久;10 · 分布式系统的硬道理 摆出了病理:部分失败、灰色失败,以及自动化在分区时按规则执行却放大事故。这一章把它们拧成一根绳:既然失败必然发生,而且很多时候分不清组件是死了还是只是慢,那一个系统该长成什么样,才能在零件不断坏掉的同时整体不倒? 这就是为失败而设计,也就是 Design for Failure。
这一章的好消息是:它几乎不靠新名词,全靠生活常识。 保险丝、船舱、留余量、丢车保帅——你早就懂这些道理,本章只是把它们安到系统上。读起来会比上一章轻松很多。
还是那条主线:AI 几秒就能给你写出一个能跑的 happy path。但这个依赖挂了要不要降级、重试几次才不算雪上加霜、丢哪部分流量保哪部分,这些判断的代价由你的线上事故承担,AI 给不了。
一、思维翻转:从多久坏一次到坏了多久能恢复
新人谈可用性,潜台词往往是尽量让它别坏:用更贵的服务器、更严的测试、更小心的发布。这条路有个天花板,因为你永远消灭不了失败。磁盘会坏、网线会被挖断、依赖会超时、有人会手抖打错一条命令。本章的真实案例里,你会看到这种手抖反复出现。
AWS 的 CTO Werner Vogels 把这句话钉进了一代工程师的脑子里:
Everything fails, all the time. 一切都会坏,随时都会坏。
一旦你接受这个前提,度量的重心就会发生一次根本的转移:
旧思维:盯着 MTBF 新思维:盯着 MTTR
───────────────────── ─────────────────────
MTBF = 平均无故障时间 MTTR = 平均恢复时间
多久坏一次? 坏了之后,多久能好?
越大越好 ← 努力方向 → 越小越好
可用性 ≈ MTBF / (MTBF + MTTR)
↑ 把 MTBF 推到无穷大,代价是天文数字、且仍有上限
↓ 把 MTTR 压到几秒,可用性照样很高 —— 而且现实可达这俩都能抬高可用性,但性价比天差地别。把代表少出故障的 MTBF 从 30 天提到 60 天,要砸的钱和精力是指数级的,且总有意外;而把代表快恢复的 MTTR 从 30 分钟压到 30 秒,靠的是自动检测、自动切换、隔离爆炸半径这类可设计、可演练的工程手段。
架构智慧:可用性的杠杆,绝大多数压在 MTTR 那一端,而不是 MTBF。一个天天有小毛病、但每次几秒就自愈的系统,比一个半年不出事、一出事就瘫一天的系统,可用性高得多,也健康得多。韧性不是从不摔跤,而是摔了能立刻爬起来,而且只蹭破一点皮。
这就是为失败而设计的全部出发点:你的精力不该全花在祈祷它别坏,而该花在假设它一定会坏时系统会怎样。下面六节,就是把这个问题拆成可操作的判断。
二、级联失败:一个慢依赖,如何拖垮全站
最反直觉、也最致命的一件事是:线上大事故极少由单个组件挂掉直接造成,绝大多数是一个组件变慢或挂掉后引发连锁反应,把健康的部分也拖垮。这叫级联失败,也就是 cascading failure,是把局部故障放大成全站雪崩的核心机制。
看一个最经典的剧本:一个慢依赖如何顺着调用链逆流而上,吃掉整个系统:
正常时: 网关 ──▶ 服务A ──▶ 服务B ──▶ 数据库
(每个请求占用 1 个线程/连接,几十毫秒就还回去)
❶ 数据库变慢(GC / 锁 / 热点),B 的调用从 50ms 涨到 5s
│
❷ B 的线程卡在等数据库上,迟迟不释放 → B 的线程池被占满
│
❸ A 调 B 也开始超时;A 的线程同样卡在等 B 上 → A 线程池耗尽
│
❹ A 一卡,客户端/网关开始重试 → 请求量不降反升,翻倍、三倍
│ ↑ 火上浇油:本就过载,重试又加压
▼
❺ 整条链路线程/连接池全部耗尽 → 健康的接口也无线程可用 → 全站 503
└─ 一个慢 DB,十分钟内拖垮了整个平台这里藏着三个把局部故障放大成全局灾难的放大器,每一个都值得你刻进脑子:
- 资源耗尽:慢调用的本质,是让请求长时间占着线程和连接不放。线程池、连接池是有限的;一个慢依赖会像泡水的海绵一样,把整个池子吸干。慢比快速失败更可怕,因为快速失败至少立刻把资源还回来了。
- 重试风暴:系统一旦变慢,客户端、网关、SDK 这些上游的善意重试会让请求量瞬间翻几倍。本来就过载,你还往里灌更多,这就是火上浇油。大量事故的致命一击,都是 retry storm 贡献的。
- 超时层层堆叠:如果每一层都设 30s 这类相同的超时,最内层慢了,外层每一层都会干等满 30s 才放弃。资源被集体罚站,健康的请求挤不进来,等于跟着陪葬。
架构智慧:慢,是比宕机更隐蔽、更致命的故障形态。宕机会快速失败,调用立刻报错,资源立刻释放;慢会让请求抱着资源一点点死掉,顺着调用链把资源耗尽,一节一节传染上去。韧性工程的一大半功夫,就是不让慢传播开:要么快速失败,要么把它关进笼子。下面三到六节,讲的全是关笼子的招。
三、隔离爆炸半径:把故障关进小格子
既然失败必然发生、还会级联传染,那第一道工程哲学就不是消灭故障,而是控制爆炸半径,也就是 blast radius:让任何单点的失败,只能炸掉一小格,炸不到全局。这是从船舶工程借来的智慧。
舱壁 Bulkhead:把资源池切开,故障不串味。 轮船的船体被隔成多个水密舱,一个舱进水,水密门一关,船照样浮着;若整个船舱是连通的,一处破洞就能沉掉整艘船。泰坦尼克的事故正是因为隔舱不够高,水漫过舱壁顶才扩散开。映射到系统:
❌ 共享一个池(一损俱损):
所有下游调用 ──▶ [ 同一个线程池 / 连接池 ]
↑ 慢依赖 X 占满整个池 → 调用健康依赖 Y 的请求也没线程了 → 全挂
✅ 舱壁隔离(各占各的):
调用支付 ──▶ [池 P:20 线程] ← 支付挂了,最多用尽这 20 个
调用推荐 ──▶ [池 R:10 线程] ← 推荐挂了,只影响推荐,支付毫发无伤
调用搜索 ──▶ [池 S:10 线程] ← 互不侵占,一个舱进水,其余照常Cell-based 架构:把整个系统复制成多个独立细胞。 更高一层的隔离,不是隔离一个池,而是把整套服务栈(网关、服务、数据)复制成多个互相隔离的 cell,每个 cell 服务一部分用户。一个 cell 整个烧了,只影响落在它里面的那批用户,其余 cell 毫无感知。AWS 大量内部服务用这种 cell-based 架构给爆炸半径设上限。
Shuffle sharding:用随机组合,让连坐概率趋近于零。 这是 AWS 的一个精妙发明。
💧 深水区,初读可跳过,记住下面这个比方就够了。 它就像发扑克牌。给每个客户随机发一手牌,也就是几个节点的组合,而不是让一整桌人共用同一手牌。这样当某个毒客户把自己那几张牌玩坏时,几乎不会有别人和他拿到完全相同的一手牌。别人顶多和他重了一两张,手里还有别的牌能正常用。于是一个坏客户拖垮一整组人的连坐,被摊薄到几乎不可能发生。下面是把这个比方算成数字的版本。
假设你有 8 个后端节点、要服务很多客户:
普通分片:把客户切成 4 组,每组固定 2 个节点
→ 某 2 个节点被一个毒客户打挂,固定绑这 2 个节点的那一整组客户全遭殃
Shuffle sharding:给每个客户随机分配 2 个节点的组合
→ 从 8 个节点里随机挑 2 个,一共有 28 种不同的组合(这就是 C(8,2)=28)
→ 两个客户恰好抽到完全相同的那 2 个节点的概率,只有 1/28,很低
→ 一个毒客户打挂它那 2 个节点,几乎不会和别人完全重叠
别人哪怕共享了其中 1 个节点,还有另 1 个能用 → 受影响面被摊薄到接近 0节点越多、每人分到的越多,组合数会爆炸式增长。上千节点时组合数是天文数字,两人完全撞车的概率低到可以忽略。这就是 shuffle sharding 威力的来源。
架构智慧:隔离的核心判断,是先想清楚故障域,也就是 failure domain,边界画在哪:哪些东西必须一起死,哪些绝不能互相拖累。最该被舱壁隔开的,永远是核心和非核心。别让猜你喜欢挂掉时,把下单支付一起带走。隔离不是免费的,更多池意味着更多闲置资源和更复杂的容量规划,所以它本身也是一道取舍:把隔离的颗粒度,花在绝不能被拖垮的关键路径上。
四、主动自保:熔断、超时预算、降载
隔离是被动防线,负责把故障关在格子里。但格子里那个组件还在挣扎,慢调用和重试本身就在消耗资源。所以还需要主动自保:系统要能识别危险、主动断舍离,而不是傻等着被拖死。
熔断器 Circuit Breaker:像家里的保险丝,自动跳闸。 当对某个依赖的调用失败率超过阈值,熔断器跳闸,后续调用直接 fail fast,根本不发出去。这样既保护了已经奄奄一息的下游,别再压它;也让自己立刻释放资源,别再傻等。它有三个状态:
失败率超阈值
┌────────┐ ───────────▶ ┌────────┐
│ 关闭 │ │ 打开 │ ← 跳闸!所有调用直接快速失败,
│ Closed │ │ Open │ 不再打扰奄奄一息的下游
│ 正常放行 │ ◀─────────── └───┬────┘
└────────┘ 探测成功 │ 冷却一段时间后
▲ ▼
│ ┌──────────────┐
└───────────── │ 半开 Half-Open │ ← 试探性放一个请求过去
连续成功 └──────────────┘ 成功→关闭恢复;失败→重新打开Netflix 的 Hystrix 把熔断器 + 舱壁打成了一个工业级组件,是这套思想最有名的落地。它的半开态是灵魂:不盲目恢复,而是先放一个探子过去试水,确认下游真活过来了,才彻底恢复。
超时预算 Timeout Budget:给整条链路一个总时限,逐层递减。 上一节说过,各层超时设得一样长会让资源集体罚站。正确做法是让超时沿调用链层层递减:上游给下游的时间预算,必须小于它自己剩下的时间:
用户能忍的总时限:3s
▼
网关(预算 3s) ──▶ 服务A(分到 2.5s) ──▶ 服务B(分到 1.5s) ──▶ DB(0.8s)
每一层都给下游更少的时间,留出余量给自己处理和返回
反模式:每层都设 30s → 最内层卡住,外层全部干等满 30s,资源被集体罚站背压 Backpressure 与降载 Load Shedding:扛不住时,主动丢一部分,保住整体。 这是最反直觉、却最体现成熟度的一招。当请求量超过处理能力,你有两个选择:
❌ 来者不拒(假装能扛):
请求洪水 ──▶ 队列无限堆积 ──▶ 内存爆 / 延迟飙到分钟级 ──▶ 全员超时 → 全军覆没
↑ 谁都没服务好,大家一起死
✅ 主动降载(丢车保帅):
请求洪水 ──▶ [超过容量?] ──是──▶ 立刻拒绝多余的(快速返回 429/503,甚至排队)
│否
▼
正常处理 ──▶ 被放进来的请求,都得到了正常、快速的服务
↑ 牺牲一部分,换大多数人的可用架构智慧:优雅地拒绝一部分请求,远胜于假装全部都能扛、然后一起崩。这是从乐观走向成熟的分水岭。背压是向上游明确表达我满了,你慢点发;降载是我自己决定超出的部分立刻丢,保住放进来的那批请求被好好服务。一个会主动降载的系统,在过载时仍然部分可用;一个来者不拒的系统,在过载时很容易全部不可用。这正是 模型推理服务 面对超额请求时要排队、要拒绝的原因:GPU 就那么多,硬接只会让所有人一起超时。
五、聪明地重试:退避 + 抖动,且必须以幂等为前提
重试是把双刃剑:用对了能自动跨过瞬时抖动,用错了就是第二节那场重试风暴的元凶。聪明地重试,要同时满足三个条件。
① 指数退避 Exponential Backoff:别催命,越等越久。 失败后别立刻重试,而是等待时间指数级拉长,例如 1s → 2s → 4s → 8s。给下游喘息的时间,而不是失败后立刻又怼上去。
② 抖动 Jitter:打散同时重试,这是关键中的关键。 只有退避还不够。如果一千个客户端同时失败、又用完全一样的退避节奏,它们会在第 1s、第 2s、第 4s 整整齐齐地一起重试,形成一波波同步的冲击波,反复把刚要恢复的下游再打趴下。加入随机抖动,也就是在退避时间上叠加一个随机量,才能把这一千个客户端的重试时刻打散开:
❌ 无抖动:故障恢复瞬间,所有客户端同步重试 → 一波波尖峰把下游反复打死
请求量 │ ▲ ▲ ▲
│ ╱│╲ ╱│╲ ╱│╲ ← 同步的冲击波
│────╯ │ ╰───────╯ │ ╰───────╯ │ ╰──
1s 2s 4s
✅ 有抖动:把每个客户端的重试时刻随机打散 → 削平尖峰,下游平稳恢复
请求量 │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ← 被摊平的、可承受的负载
│───────────────────────────────AWS 的工程实践和 Marc Brooker 的经典文章《Exponential Backoff And Jitter》都说明:在大量客户端争抢时,加了抖动的退避能把无效的重复调用砍掉一半以上,完成时间也显著更短。抖动是那种一行代码的改动,换来数量级稳定性的高杠杆设计。
③ 重试预算 Retry Budget + 幂等前提。 还要给重试上一个总闸:限制重试请求占总请求的比例,比如不超过 10%,一旦超过就停止重试。这是从根上掐死重试风暴的保险。而所有这一切的大前提是:
重试的对象,必须是幂等的。 否则一个超时了但其实成功了的请求再次执行,就会重复扣款、重复发货、重复下单。
这一条直接承接 11 · 数据一致性工程 的核心:at-least-once 重试 + 消费端幂等 = 效果上的恰好一次。 支付系统 的幂等扣款、通知系统 的去重限频,就是敢于重试的地基。没有幂等,你根本不敢重试;不敢重试,瞬时故障就成了永久失败。
架构智慧:重试不是失败了就再试一次这么天真。安全的重试 = 指数退避 + 抖动 + 重试预算 + 幂等前提,四样缺一不可。少了退避和抖动,重试会变成压垮下游的风暴;少了预算,风暴没有上限;少了幂等,重试会把数据搞错。AI 生成的代码默认是
retry(3)式的裸重试,这恰恰是最危险的那种。
六、优雅降级:核心保命,非核心可弃
前面几节都在防止崩。但有时候下游就是挂了、就是恢复不了,这时韧性的最后一道防线是:坏一部分,远好过全挂。这就是优雅降级,也就是 Graceful Degradation。
核心判断是一道你必须提前做好的功课:把功能按掉了会死和掉了能忍分级。
电商大促,推荐服务挂了。两种活法:
❌ 没分级(一损俱损):
推荐挂 ──▶ 商品详情页加载推荐时报错 ──▶ 整个详情页打不开 ──▶ 用户连下单都做不了
↑ 因为猜你喜欢,丢了成交
✅ 优雅降级(弃车保帅):
推荐挂 ──▶ 猜你喜欢模块显示默认热榜 / 干脆不显示
──▶ 商品详情、加购、下单、支付全部照常
↑ 用户几乎无感,核心交易一分钱不少实现优雅降级的工程抓手,是降级开关,也叫功能开关 feature flag:把每个非核心功能都做成可以一键关闭的开关。事故现场或大促前,运维一键关掉猜你喜欢、实时弹幕、个性化排序,把宝贵的 CPU、数据库连接和下游配额让给核心交易链路。降级的常见形态:
- 降级到缓存 / 默认值:推荐挂了,返回一个预置的热榜;实时库存查不到,先显示有货让用户下单,后端再校验。
- 降级功能丰富度:大促时关掉个性化,所有人看同一个榜单,省掉昂贵的实时计算。
- 降级到异步:同步处理扛不住,先收下请求、丢进队列、返回处理中,慢慢消化。
架构智慧:优雅降级的前提,是你早就想清楚了什么是核心、什么是非核心。这件事必须在风平浪静时做完,绝不能等事故现场才临时拍脑袋。一个没有降级预案的系统,在过载时只有全开和全关两个挡位;一个有降级预案的系统,有一整排可以逐级松开的泄压阀。这一条紧扣 06 章 的判断:大促时关掉猜你喜欢,保住下单支付。它不是一句口号,而是一套需要提前埋好开关的工程能力。
七、量化可靠性:SLI / SLO / SLA 与错误预算
讲了这么多手段,一个绕不开的问题是:到底要做到多可靠? 06 章 已经警告过,别一开口就要五个 9,因为每多一个 9,成本都会数量级上涨。但可靠性目标不能靠感觉拍板,得有一套可量化、可管理的语言。Google SRE 把它讲透了,三个词先分清:
SLI (Indicator 指标) ── 你测量的那个数:成功率?P99 延迟?
例:过去 5 分钟,成功响应数 / 总请求数
SLO (Objective 目标) ── 你给 SLI 定的内部目标:成功率 ≥ 99.9%
例:一个季度内,99.9% 的请求成功且 < 300ms ← 团队自己的及格线
SLA (Agreement 协议) ── 写进合同、违约要赔钱的那条线
通常松于 SLO(留安全垫):对外承诺 99.5%,内部 SLO 卡 99.9%而把它们用活的,是错误预算,也就是 Error Budget,这个绝妙的发明:
既然 100% 不可能,就定 SLO = 99.9%
↓
那么 0.1% 就是你被允许出错的额度 = 错误预算
(一个月 ≈ 43 分钟的可以挂的时间)
↓
预算还有剩 ──▶ 大胆发新功能、上线、搞实验(反正还输得起)
预算烧光了 ──▶ 冻结一切上新,全员转去搞稳定性,直到把预算攒回来架构智慧:错误预算是韧性工程里最聪明的政治发明。它把研发和 SRE 之间关于稳定与迭代速度的长期拉扯,从靠嗓门大变成用一个共享数字说话。它还揭示了一个反直觉的真相:追求 100% 可靠是错的。 100% 意味着你永远不敢发布、不敢实验,迭代速度归零,而用户其实根本感知不到 99.9% 和 100% 的区别,网络本身也没那么可靠。留出错误预算,是在主动给自己留出冒险和迭代的空间。几个 9 的选择,本质是 06 章 那句:回到业务问这个系统真的需要那么多 9 吗。多出来的每个 9,都是从迭代速度和真金白银里换的。
八、混沌工程:用主动注入故障,证明韧性
最后一个、也是最颠覆认知的判断是:你以为系统有韧性,和系统真的有韧性,是两回事。 前面六节的所有设计,包括熔断、降级、舱壁、超时,在没被真正触发过之前,都只是理论上能扛。而没演练过的容灾预案,约等于没有。
悖论:那些只有故障时才会触发的代码路径,比如熔断逻辑、降级分支、failover 切换,恰恰是平时跑得最少、测试最薄、最可能悄悄坏掉的路径。等到真出事那一刻,你才发现降级开关三个月前就失灵了,failover 从库其实没在同步。这是事故现场最常见的二次打击。
混沌工程 Chaos Engineering 的回答简单而生猛:别假设,去证明。 主动、可控地往生产系统里注入故障,在可控的小爆炸中提前暴露那些等真出事才会暴露的脆弱点。它发源于 Netflix,当年为了上云,他们做了 Chaos Monkey:
Chaos Monkey:在工作时间随机杀掉生产环境里的实例
│
└─▶ 逼着每个团队从第一天起就假设我的实例随时会被杀
→ 于是没人敢依赖单个实例不挂 → 冗余和自愈成了默认习惯
后来扩成 Simian Army(猴子军团):
• Latency Monkey ── 注入延迟,演练慢依赖(正是第二节那个杀手)
• Chaos Gorilla ── 干掉整个可用区,演练区域级容灾
• Chaos Kong ── 干掉整个区域,演练最高级别的灾难恢复它的精髓不是搞破坏,而是一套科学方法:先定义正常状态的稳态指标,例如成功率;再提出假设,比如杀掉一个实例后成功率也不该掉;然后在生产或仿真环境注入故障,看假设是否成立,不成立就修。它把希望它能扛,变成已经验证过它能扛。
架构智慧:混沌工程是为失败而设计这一整章的验收环节。它逼你把纸上的韧性变成跑过的韧性。这背后是一个深刻的工程心态转变:与其在凌晨三点被一场没预料到的真故障叫醒、手忙脚乱,不如在周二下午、喝着咖啡、用一场你完全掌控的计划内故障,提前找出同样的弱点。主动找痛,是为了不被动挨打。当然,它有严格前提:必须先有监控能看见影响、有爆炸半径控制能及时止损、能一键中止。没有这些护栏就往生产注故障,那不叫混沌工程,叫事故。
📌 真实案例:三次手抖,如何拖垮半个互联网
韧性工程最好的老师,是真实的大型事故。下面三个都来自官方 post-mortem,每一个都精准踩中了本章的某根筋。
① AWS S3 大故障(2017-02-28):一条打错的命令,如何拖垮大半个互联网。 那天上午,一名 S3 工程师按既定排查手册执行命令,本想下线少量计费子系统的服务器,但一个参数输入有误,导致下线的服务器远多于预期,其中误伤了支撑 S3 索引子系统和放置子系统的大批服务器。索引子系统负责管理整个区域所有对象的元数据和位置。这两个子系统被迫完全重启;而在 S3 的体量下,重启意味着要重建数十亿对象的元数据索引,耗时远超预期。由于无数服务,包括 AWS 自己的控制台、乃至大量第三方网站,都依赖 us-east-1 的 S3,大半个互联网随之瘫痪约 4 小时。
教训精确对应本章:① 手抖是常态,Vogels 说 everything fails 时也包括人;② 爆炸半径失控,一条命令能误伤如此大范围,说明缺乏隔离与危险操作的二次防护;③ AWS 事后的整改正是给这类命令加上移除容量不得超过最小安全阈值的护栏,本质就是给操作装一个降载式的下限保护。 📎 AWS 官方事后分析:Summary of the Amazon S3 Service Disruption (US-EAST-1)
② Meta / Facebook 全球宕机(2021-10-04):一次配置变更,如何让 35 亿人消失约 6 小时。 一次例行维护中,一条本想评估骨干网容量的命令,意外把整个骨干网的连接全部撤下;而一个审计工具的 bug 没能拦住这条错误命令。骨干网一断,Facebook 的 DNS 服务器因检测到自身与数据中心失联,主动撤回了自己的 BGP 路由通告。于是从全世界的角度看,Facebook 的 DNS 直接从互联网上消失了:服务器其实还活着,但全世界都找不到它。Facebook、Instagram、WhatsApp、Messenger 全球下线约 6 小时。雪上加霜的是:连内部工具、门禁系统都依赖这套网络,工程师一度连机房都进不去、远程也连不上,恢复因此被严重拖慢。
教训精确对应本章:① 这是级联失败的教科书。一个局部动作,经由健康检查到自动撤回路由的自动化链条,放大成全局灾难,与 10 章 GitHub 那例如出一辙:自动化在极端情况下按规则执行,却闯了大祸;② 恢复工具不能依赖正在恢复的那个系统,这是韧性设计里极易被忽视的循环依赖,直接拉长了 MTTR。 📎 Meta 官方事后分析:More details about the October 4 outage
③ Cloudflare 全球故障(2019-07-02):一个正则表达式,如何在几秒内打满全球 CPU。 一次 WAF,也就是 Web 应用防火墙,规则的常规上线中,有一条规则含有一个写得不好的正则表达式,触发了灾难性回溯 catastrophic backtracking,CPU 开销爆炸式增长。致命的是:这条规则没有灰度、没有金丝雀发布,被一次性推送到了全球所有边缘服务器。结果全球每一个处理 HTTP/HTTPS 流量的 CPU 核心瞬间被打满,Cloudflare 网络大面积瘫痪约 27 分钟。它当时承载着可观比例的互联网流量。
教训精确对应本章:① 资源耗尽,这次是 CPU,和线程池耗尽是同一种病:某段逻辑抱着资源不放,把整体拖垮;② 变更没有爆炸半径控制,全球一次性推送把局部 bug 放大成全局灾难,这正是 cell-based / 金丝雀发布要解决的问题;③ Cloudflare 事后重新加回了被误删的 CPU 用量保护、并改用有运行时上限保证的正则引擎,本质都是给可能失控的东西装上限。 📎 Cloudflare 官方事后分析:Details of the Cloudflare outage on July 2, 2019
三个案例,三种手抖或小 bug,共同的剧本是:一个微小的局部触发,顺着资源耗尽、自动化连锁、全局推送这些放大器,炸成了全局灾难。韧性工程要做的,就是在每一个放大器上装闸:隔离、熔断、降载、爆炸半径控制、灰度发布。
🤖 AI / vibe coding 视角:happy path 原型,与人补的韧性判断
韧性是 AI 时代含金量不降反升的判断力,原因有两层。
第一层:AI 生成的代码,默认只有乐观路径,也就是 happy path。 你让 AI 写一段调用支付接口的代码,它会给你一段在网络通、对方秒回、一切正常前提下完美运行的代码,但默认没有超时、没有重试退避、没有熔断、没有降级、没有隔离。这正是本章讲的所有东西的反面。vibe coding 的产出,是一个在 demo 里跑得飞快、一上生产就脆得像玻璃的原型:
AI 默认给你的(happy path) 生产真正需要的(人补的韧性判断)
──────────────────────── ──────────────────────────────────
result = call(payment_api) + 超时(别无限等)
# 假设它一定成功、一定秒回 + 退避 + 抖动重试(且接口必须幂等)
+ 熔断(对方挂了别再压它、别拖垮自己)
+ 降级(挂了走兜底,而不是整页崩)
+ 舱壁(用独立池,别拖垮其他调用)
↑ 把这个脆弱的玻璃原型,变成扛得住生产的系统,靠的正是这一整列人补的判断把脆弱的 happy-path 原型,锻造成扛得住生产的系统,这中间的全部距离,就是这一章。AI 能瞬间帮你写出熔断器的代码,但这个依赖该不该熔断、阈值设多少、降级到什么、丢哪部分流量,都是判断题,代价由你的线上事故承担。
第二层:AI 原生系统本身,把韧性的需求又拔高了一截。 因为它引入了一种新的失控形态——自主循环烧钱:
- AI Agent 平台 的步数、成本、超时上限,本质就是本章的降载 + 熔断:一个会规划、调工具、再规划的自主 agent,若不设硬上限,可能原地打转、无限循环,一晚上烧掉天文数字的 token 费。给自主循环装的这些刹车,正是韧性思想在 AI 时代的新形态。自主性越高,越要有硬性的熔断与降载。
- 模型推理服务 面对超额请求,GPU 就那么多,只能排队 + 降载:接不下的请求快速拒绝,返回 429,而不是全塞进来让所有人一起超时。这正是第四节降载判断的直接应用。
- Agent 的工具调用要幂等 + 可重试、长任务要靠检查点可恢复来处理部分失败。这些都站在本章和 10、11 的地基上。
架构智慧:LLM 把一层新的不确定性叠在系统原本的不确定性之上,模型会跑偏、会幻觉、会陷入循环。所以 AI 原生系统不是更不需要韧性,而是更需要、且需要新形态的韧性,比如给自主循环装刹车。vibe coding 让写代码变快了,但让脆弱的原型扛得住生产这件事的价值只升不降。它恰恰是人最该补、AI 最补不了的那部分判断。
🎯 随堂检验
本章小结
- 核心思维翻转:高可用不是祈祷别出事,是预设一定会出事。Vogels 那句 Everything fails, all the time 讲的就是这个。度量重心从 MTBF 转向 MTTR,也就是从多久坏一次转向坏了多久能恢复。韧性是摔了能立刻爬起来、只蹭破一点皮。
- 级联失败是头号杀手:大事故极少只是一个组件挂了,更多是一个组件慢了,再经由资源耗尽、重试风暴、超时堆叠这三个放大器,把全站拖垮。慢,比宕机更致命。
- 隔离爆炸半径:舱壁切开资源池,cell-based 复制成独立细胞,shuffle sharding 用随机组合让连坐趋近于零。核心是先画清故障域,把核心和非核心舱壁隔开。
- 主动自保:熔断器有关闭、打开、半开三态,像保险丝跳闸后再探测恢复;超时预算沿链路递减;背压与降载负责主动丢一部分保整体。优雅地拒绝一部分,远胜假装全扛、然后一起崩。
- 聪明地重试:指数退避 + 抖动 + 重试预算 + 幂等前提,四样缺一不可。少了任何一个,重试都可能变成风暴的元凶。
- 优雅降级:提前把功能按掉了会死和能忍分级,用降级开关一键关非核心,把资源让给核心链路。坏一部分,远好过全挂。
- 量化可靠性:SLI 是测量值,SLO 是内部目标,SLA 是合同线;错误预算把稳定与速度之争变成用一个共享数字说话。追求 100% 是错的,留预算是为了留出迭代和冒险的空间。
- 混沌工程:别假设,去证明。主动注入故障,例如 Chaos Monkey / Simian Army,在可控小爆炸里提前暴露脆弱点。前提是有监控、有爆炸半径控制、能一键中止。
- AI 时代主线:vibe coding 默认只给乐观路径,没有超时、重试、熔断、降级、隔离;把脆弱的 happy-path 原型变成扛得住生产的系统,靠的正是人补的韧性判断。而 AI 原生系统里 agent 的步数和成本上限,本质也是降载 + 熔断,只会让韧性更吃重。
承上启下:这一章解决的是系统会不会倒,也就是在零件不断坏掉时如何不崩。下一章(进阶篇第 4 章)《13 · 规模化的力学》换一个维度:当系统不是坏,而是被成功撑爆,用户、数据、流量涨了百倍千倍,架构会在哪里先裂开?分片、热点、缓存、异步化这些规模化的力学,又该怎么提前布局。韧性让你扛住故障,规模化让你扛住成功。
💬 评论