专业书籍精读 · Chaos Engineering · 第 12 章
Chaos Engineering: System Resiliency in Practice · Ch 12 · The Experiment Selection Problem (and a Solution) · 2020
你刷视频时,背后有上百个小程序在接力干活:一个查你是谁,一个挑推荐,一个找视频文件……混沌工程就是故意弄坏其中一些,看整体还撑不撑得住。
可问题来了:弄坏哪几个?坏一个的选法有一百种,同时坏两个就有将近五千种,坏三个是十几万种。一次只能试一种,挑错了就白试。这一章专门回答「该先试哪个」。
想知道一座桥哪里最脆弱,直觉是盯着它出事的样子看。这一章反过来说:先认真看它平时是怎么好好站着的。
弄清楚「这次播放能成功,是靠了哪几根柱子」,你就知道:只踢那些没在扛重的柱子,什么也学不到;要踢,就踢正在扛重、而且没人替补的那几根。
每次请求成功,都让系统记一本「功劳簿」:这次是谁帮了忙、谁又是谁的备胎。
像侦探排查不在场证明:先搞清楚每个人「是怎么做到的」,再去针对性地拆。
好处是试的次数从「天文数字」降到「一张短清单」,而且每次试都有理由,还能顺便把老师傅脑子里的直觉变成机器能算的东西。
一句诚实的代价:前提是系统得能把「谁帮了忙」记清楚,记不全的地方,这套方法也会看漏。
别乱踢,也别只靠老师傅:先看系统怎么成功的,再专挑能让它失败的最小组合去试——每试一次,要么抓到漏洞,要么发现一个新备胎。
想看组合爆炸怎么算、血缘图和求解循环长什么样、在 Netflix 怎么落地? → 切到精读版
你以为找漏洞要盯着失败看,其实该注入什么故障,答案藏在成功的请求里:把一次成功的「凭什么成功」记成血缘图,就能算出最少要打掉哪几个支撑才可能让它失败——只试这些组合,把无穷的故障空间缩成一张短清单。
全书第 12 章,Part III「人的因素」收尾篇,作者 Peter Alvaro(加州大学圣克鲁兹分校教授,LDFI 的提出者)。Ch11 刚说完「选什么实验是人的判断」,本章接着问:这份判断能不能被机器复现、规模化;下游 Ch16 的持续验证正是这条路的工程化。
设想一个有 100 个服务的系统。只注入单点故障,候选是 100 个;任意两个同时故障是 C(100,2) = 4,950 种;三个同时故障是 161,700 种——还没算延迟、丢包、部分失败这些故障类型。每个实验都要占生产流量和观察时间,一天能跑几十个已算勤快。
更糟的是,成熟系统里真正致命的 bug 往往恰好藏在组合里:单个服务挂掉早就有降级兜着(否则早被发现了),只有「主路径和它的备胎同时出事」才会穿透。所以实验选择不是锦上添花的优化,而是决定混沌工程是否还能找到新东西的核心问题。不解决它,团队要么在海量无聊故障上烧资源,要么把「选什么」全押在少数专家的直觉上。
随机搜索以 Chaos Monkey 为代表:随机杀实例。优点是零先验、无偏见、实现简单,对「单点故障没兜住」这类浅层问题极其有效。但它的命中率随组合深度指数下降——若致命组合是某特定的两个服务同时出事,随机两两注入平均要试上千次才撞上一次。
专家指引是游戏日(game day)的模式:资深工程师凭对系统的理解指出「这里最可疑」。这在实践中很有效,许多最有价值的实验正出自专家之手。问题有三:不随人数之外的东西扩展(专家时间就是上限);难以传授(直觉说不清来源);以及最根本的——混沌工程要检验的正是人的心智模型,用同一个模型去挑实验,盲区会原样保留。
Alvaro 的关键一步是先研究成功。一次请求成功返回,背后有一组「支撑」:哪些服务被调用并返回了正确结果、哪些消息被送达。把这些因果依赖记下来,就是这次成功的血缘(lineage),概念来自数据库里的数据溯源(data provenance)。
血缘的价值在于它同时暴露了冗余。看一个例子:首页请求要成功,需要鉴权服务 A,并且推荐服务 B 或其降级的热门榜 C 之一可用,并且主库 D 或副本 E 之一可用。写成逻辑式就是 A ∧ (B ∨ C) ∧ (D ∨ E)——白话说:A 必须在,B、C 至少一个在,D、E 至少一个在。
五个组件的子集有 32 个,随机挑大多是浪费;而能让这次请求失败的最小组合只有 3 个。这就是把专家的直觉「主路径和备胎一起断才危险」变成了可计算的东西。
血缘驱动故障注入(Lineage-Driven Fault Injection, LDFI)把上面的推理自动化,可以把它看成一位不知疲倦、只按证据推理的专家,循环如下:
LDFI 最早在学术原型 Molly 上实现(SIGMOD 2015),针对的是用逻辑语言写的协议模型;本章讲的是它走出实验室、接到 Netflix 生产环境的版本。那里没有现成的「程序血缘」,于是用分布式追踪的调用树近似血缘,用 Netflix 已有的故障注入平台做执行,并且要处理现实噪声:同类请求每次走的路径不完全相同、服务一直在发版变化。
本章的大框架是:可观测性基础设施本来是为排障建的,但它记录的「请求怎么成功」恰好是挑实验所需的知识。专家之所以准,是因为他们脑中有一张近似的依赖与冗余图;追踪系统把这张图从脑子里搬到了数据里,于是可以被机器读、被机器推理。这条路的本质是把直觉工程化——不是取代专家,而是让专家的推理方式在没有专家的地方也能运行。
三种挑法不是替代关系,而是前提条件、成本与能找到的 bug 深度的交换。
| 挑法 | 前提 | 擅长找 | 扩展性 | 代价 / 盲区 |
|---|---|---|---|---|
| 随机搜索 | 几乎无,能杀实例即可 | 单点故障没兜住的浅层问题 | 随机器扩展,随组合深度崩溃 | 深层组合命中率极低;大量「什么都没发生」 |
| 专家指引 | 有懂系统的资深工程师 | 领域相关的高价值组合 | 上限 = 专家时间 | 继承专家心智模型的盲区;难传授 |
| 穷举 | 空间足够小 | 一切 | 100 个服务的三元组已 16 万 | 生产上不可行,只适合小模型 / 测试环境 |
| LDFI | 请求级追踪 + 「成功」的判据 + 可编程注入平台 | 主路径与备胎同时失效的组合 | 随计算扩展;每轮都在收窄 | 追踪看不到的依赖就推不到;建设成本高 |
判据:没有追踪就先随机 + 专家;追踪覆盖到关键路径后,LDFI 才有饭吃。
这章把混沌工程从「工具」推向「搜索问题」:注入本身已是商品化能力,稀缺的是挑选。面试被问「你有 200 个微服务,混沌实验从哪开始」,好的回答是:先用调用链找出无冗余的单点(如例子里的 A),再找「主 + 备」成对的依赖,按「打掉它会伤到多少核心请求」排序——这正是 LDFI 思路的手工版。
大厂实证
① 问题:故障空间随组合爆炸(100 个服务的三元组 16 万),选什么实验才是瓶颈。
② 成熟系统的致命 bug 多藏在「主路径 + 备胎同时失效」的组合里,单点注入找不到。
③ 随机搜索无偏但浅;专家指引准但不扩展,且继承专家自己的盲区。
④ 反转:先研究请求怎么成功,把支撑与冗余记成血缘图。
⑤ 血缘转成逻辑式,用 SAT 求最小致败组合;例子里 32 个子集缩到 3 个。
⑥ 循环:注入后失败 = 找到 bug,仍成功 = 发现未知冗余、补图再算。
⑦ Netflix 用分布式追踪近似血缘,把 LDFI 接进生产(SoCC 2016)。
⑧ 盲区:追踪看不到的依赖、共享资源、延迟类故障,以及错误的成功判据。