专业书籍精读 · Chaos Engineering · 第 3 章
Chaos Engineering: System Resiliency in Practice · Ch 3 · Casey Rosenthal & Nora Jones · 2020
前两章把坏消息说完了:现在的线上系统太复杂,没有谁装得下它;而且它每天都被几股正当的力气往危险处推一点,偏偏「安全」这条线不会提前喊疼。这一章开始给办法——而且给的是一套规矩,不是一堆工具。
这两件事听着差不多,其实差很远。
消防检查是拿着清单一条条对:灭火器在不在、安全出口堵不堵。它能查出来的,只有你清单上已经写了的那些。
消防演练是某天早上真的拉响警报,看一千个人到底怎么跑、几分钟能跑完、哪个楼梯口会堵死。它会告诉你清单上没有的事。
这一章说的就是:别再只做检查,要做演练;而且演练得有规矩,不然就是扰民。
过去我们对系统的信心几乎全来自「检查」:写测试、过评审、盯监控面板。这些都有用,但有个共同前提——你得先知道该检查什么。而复杂系统最要命的地方正是:真正出事的那个组合,你压根没想到过,所以它不在任何清单上。于是你会得到一种很危险的感觉:检查全通过了,应该没问题。
最大的变化是一句话的立场:「我们从来没出过这种事」不算证据,「我们上周故意弄了一次、用户没感觉」才算。信心从「我觉得」变成了「我试过」。诚实说一句代价:这种试法在真系统上做,就一定有一小部分真实用户会被波及,这个代价归不了零,只能压小。
检查只能确认你已经知道的事,演练才会告诉你没想到的事;这一章的五条规矩,就是把「我以为它扛得住」变成一次能被当场推翻的小实验。
想进到具体机制、量级和示意图? → 切到精读版
你以为混沌工程是「在生产上搞破坏」。这一章要换掉的恰恰是「搞破坏」三个字:它是实验(experiment),不是测试(test)——测试只能确认你已经知道的事,实验才产生新知识。五条高级原则(建稳态假设、多样化真实事件、在生产跑、自动化持续跑、最小化爆炸半径)合起来只做一件事:把「我以为它扛得住」改写成一句能被当场证伪的假设,再用尽可能小的代价去否证它。否证成功就拿到真问题;没被否证,拿到的也只是「这一次、这个条件下」的信心。
全书 21 章的第 3 章,Part I「铺垫」的收尾,也是全书的方法论骨架:Ch1 证明复杂系统推不出来,Ch2 证明安全方向天然缺反馈、信号必须人工造,这一章给出造信号的规矩。往下 Ch4–Ch8 是 Slack、Google、微软、LinkedIn、Capital One 把五条原则在各自约束下的变形,Ch16「持续验证」是原则四的终点。现实里它对着的,是任何一个已有监控和回滚、但「演练」还停留在按清单检查的团队。
Ch1 和 Ch2 留下的洞是:既然推不出来、又必须人工造安全信号,那怎么造才算造对了?
这个洞不填,它会退化成两种坏形态。一种是猴子乱砸:随手杀几台机器,瞄一眼面板觉得没事,于是宣布系统有韧性。另一种更隐蔽,是演练表演:季度排一场演练,结束写一份「演练成功」的报告——判据是事后看图说话,任何结果都能解释成成功。
两者病根相同:没有把「我以为它扛得住」写成一句能被推翻的话。本章要解决的就是这个——给出一套让「造故障」真正产出知识的最小约束。不解决会怎样:团队会把「我们做过混沌工程」当成可靠性证据,而这比不做更危险,它没增加任何真实韧性,却消掉了警惕心。
本章采用的定义(也是全书通行版本)是:混沌工程是在系统上做实验的学科,目的是建立对该系统在生产环境扰动下仍能正常工作的信心。每个词都在划界,而划得最狠的是「实验」。
测试是对一个已知属性下断言:输入 A,断言输出 B,只有通过 / 不通过两种结果,它不产生新知识,只告诉你那条你早就想到的性质还成不成立。实验是对一个你不知道答案的问题取样,产出是新信息。
落到具体。「下游返回 503 时网关该返回 502 而不是直接崩」——这是测试,你事先知道正确答案,写单元测试即可。「下游 p99(一百个请求里第 99 慢的那个)从 80ms 涨到 2s 时,下单成功率还保不保得住 99.9%」——这是实验:答案取决于连接池大小、重试策略、超时级联、缓存命中率在那一刻的组合,而这正是 Ch1 说的「没人装得下」。你写不出那条断言,不是因为懒,是不知道该断言什么。
由此本章给了一条配套立场:混沌工程偏好验证(verification,只在边界上看输出对不对)而不是确认(validation,去分析内部各部分、建一套能解释它的心智模型)。Ch1 已说明后者在复杂系统上做不到,所以这门学科整个赌在前者上:不搞懂整条链路,也能问出「用户那头还好不好」——刚接手系统的团队不必先读懂它才能开始做实验。
稳态指系统正常运转时那个可度量、稳定的输出。本章对它要求很挑:必须是边界上、贴近用户的业务指标,而不是内部属性。
正典例子是 Netflix 的 SPS(每秒视频播放启动数)。它好在三处:直接对应「有人正在成功看片」;曲线形状日复一日极稳,同一时段基本可预测;而且链路上任何一环真坏了,它一定会掉。
反面教材是 CPU 使用率、某服务错误率、队列长度这类内部指标:不是不该看(排障当然要看),而是信噪比太差——CPU 在分钟级上就能抖几十个百分点,等于把信号埋进噪声。
假设的标准写法是:在 X 条件下,稳态 Y 仍保持在 Z 范围内。三个要素都不能省,尤其是 Z——判据必须事先写死:偏离多少、持续多久算否证。事后看图说话的实验不可证伪,只会退化成那场演练表演。
这条要求注入的是真实世界里确实会发生的事件,并按「发生频率 × 潜在影响」排序。两个常见的窄化都要纠。
一是以为只有「杀机器」。真实事件清单长得多:网络延迟与丢包、下游返回畸形或空响应、证书过期、时钟漂移、磁盘写满、依赖整体不可用、重试风暴、配置推错、单可用区失联,还有非技术事件——Google 的 DiRT 真的会演「关键工程师联系不上」(Ch5)。
二是以为只有「故障」。大促流量翻 5 倍、一次自动扩容、一次正常发布,都是真实事件,而且它们恰恰是很多事故的起点。
为什么排序比注入难:故障空间是组合爆炸的,注入手段却早就不缺了。本章的答案是靠真实世界的分布——你的事故库、告警历史、依赖拓扑就是现成的先验。怎么系统化排序是 Ch6(微软)的主题;Ch12 更进一步,用数据血缘算出注入点而不是靠猜。
这两条必须一起读,因为它们是一对对冲。
为什么非得在生产。你想要信心的那个系统就是生产,别的都是替身。预发至少差在六处:流量形状与并发度、数据规模与倾斜、配置漂移、依赖的版本与容量、缓存命中率、连接池水位。举个具体的:预发只有生产 1/100 的流量,连接池永远打不满——而重试风暴的核心机制恰恰是「池满→排队→超时→重试」。那条链在预发里不可能复现,不是环境搭得糙,是它本质上需要那个量级。
这是五条里最有争议的一条,本章自己也承认要渐进:Ch4 的 Slack 先写设计文档、定预案、人在回路地演一场;Ch8 的 Capital One 在合规约束下从非生产起步。
爆炸半径怎么锁死。核心手法是金丝雀式的对照:从生产流量引出很小一撮(百分之一量级),切成对照组与实验组,只给实验组注入,再比两组的稳态指标。四件配套的事缺一不可——低峰跑、一次只动一个变量、预设自动叫停、一个人人都能按的总开关。
诚实说代价:爆炸半径不可能为零。只要在生产做就一定有真实用户被波及;把它压到伦理上可接受的范围是 Ch7(LinkedIn)的主题。
手工做一次实验,得到的是一次性的信心。但系统天天在变(这正是 Ch2 的漂移):今天合进去的一个超时参数、昨天新接的一个依赖,都可能让上个月的结论作废。信心有半衰期,所以这条原则要求把实验做成常驻设施。
要自动化的是两件事,第二件更容易被漏掉:执行(何时注入、注入什么、怎么分流)和判定(两组指标怎么比、什么算显著偏离、什么时候叫停)。只自动化执行、判定还靠人盯面板,规模一上去就卡在人身上——而且人盯面板的那一刻,爆炸半径正在扩大。
Netflix 的演进路线是这条原则最好的注脚:从 Chaos Monkey(随机终止生产实例,粗暴但天天跑)走到 ChAP(Chaos Automation Platform,自动选实验、自动切两组、自动判定与叫停)。方向很明确——从「定期演一场」变成「流水线上的一道常设关卡」,即 Ch16 的持续验证(continuous verification)。
不是「在生产搞破坏」。破坏只是手段,产出是信息,目的是修。更准的说法是「在生产修东西」:实验把一个潜在事故提前兑现在你选定的时间和规模下,你用一个工作日的从容,换掉了一个凌晨三点的被动。
不是反脆弱(antifragility)。Taleb 的反脆弱主张给系统加压力源使之变强,手段常常是加冗余、加机制——而加东西就是加复杂度,Ch1 和 Ch2 已经算过这笔账。混沌工程方向相反:它不加东西,只把已经存在的弱点找出来;而且它本身不让系统变强,它只产生信息,改不改、怎么改仍然要人决定。把注入故障本身当成变强的机制,是对这门学科最贵的误读。
原则一的全部难点在「挑哪个指标」:
| 候选指标 | 边界还是内部 | 噪声水平 | 真坏了它一定掉吗 | 能当稳态吗 |
|---|---|---|---|---|
| 用户侧业务量 (SPS、下单成功率) | 边界 | 低,日复一日形状稳定 | 是 | 最合适 |
端到端成功率 / p99 | 边界 | 中,受外网波动影响 | 多数情况是 | 合适,和业务量配对用 |
| 某服务错误率 | 内部 | 中 | 不一定,重试会兜住 | 排障用,别当稳态 |
| CPU / 内存 / 队列长度 | 内部 | 高,分钟级可抖几十个百分点 | 不 | 不合适,信号埋在噪声里 |
判据就两条:在边界上、且真坏了一定会掉。两条都满足的指标,一个团队通常只有两三个。
原则三与五的张力,在不同成熟度下有不同的解。常见四档:
| 阶段 | 在哪跑 | 自动化到哪 | 爆炸半径 | 前置条件 | 代价 / 失效点 |
|---|---|---|---|---|---|
| 起步 | 非生产 / 预发 | 手工,人在回路 | 零真实用户 | 稳态指标 + 回滚 | 结论偏乐观;重试风暴这类要量级的机制复现不出来 |
| 人在回路的 生产演练 | 生产,预约时间窗 | 手工执行,人判定 | 单服务 / 单可用区,低峰 | 预案 + 总开关 + 当班人 | 占人力,频率上不去(Slack 的做法,Ch4) |
| 生产小比例 对照实验 | 生产 | 执行自动,判定半自动 | 1% 量级流量 | 按比例分流 + 分组指标 | 分流与分组指标两件基建得先建 |
| 持续验证 | 生产 | 执行 + 判定 + 叫停全自动 | 自动收口 | 以上全有 + 可编排 | 门槛最高(ChAP 那条路,Ch16) |
什么场景该选哪一档,判据可以落得很具体:
这一章最能直接用的,是一句能在评审会上改变结论的话:「我们从没出过这种事」不是证据,「我们上周故意制造了这种事、稳态指标没掉」才是。面试被问「你怎么保障可靠性」,答「写测试 + 上监控」是合格线;答「测试守已知属性,未知的那部分靠实验——定稳态指标、写可证伪的假设、在生产按小比例跑、自动判定与叫停」是另一个层级。
大厂实证
① 核心命题:混沌工程是实验不是测试——测试只确认你已知的属性,实验才产生新知识。复杂系统里你写不出断言,是因为不知道该断言什么。
② 定义:在系统上做实验的学科,目的是建立对它在生产扰动下仍能正常工作的信心——是信心,不是证明;方法论上偏好验证(只看边界输出)而非确认(搞懂内部)。
③ 原则一:围绕稳态建可证伪的假设。指标要在边界上、贴近用户、噪声小(SPS 式的),判据事先写死。
④ 原则二:多样化真实事件,按「频率 × 影响」排序。不限于杀机器,也不限于故障——流量尖峰和扩容都算。
⑤ 原则三:在生产跑。预发差在流量形状、数据倾斜、配置漂移、依赖版本、缓存命中率、池水位,重试风暴那条链本质上需要生产的量级。
⑥ 原则四:自动化、持续跑——信心有半衰期。执行和判定都要自动化,只自动执行会卡在人身上。
⑦ 原则五:最小化爆炸半径:1% 量级的两组对照 + 低峰 + 单变量 + 自动叫停 + 总开关;但半径归不了零。
⑧ 两条划界:不是「在生产搞破坏」(破坏是手段,产出是信息,目的是修),也不是反脆弱(它不加东西使之变强,只把已有弱点找出来)。
⑨ 最可用的一句:「我们从没出过这种事」不是安全证据;「我们故意制造过、稳态没掉」才是。