专业书籍精读 · Chaos Engineering · 第 7 章
Chaos Engineering: System Resiliency in Practice · Ch 7 · LinkedIn · 2020
前三章都在讲「怎么把故障注进生产」。这一章问一个被绕开的问题:生产里有真人。
你摇的不是一堆机器,是几亿个正在投简历、正在和招聘方说话的人。LinkedIn 的回答不是「那就别在生产做」,而是:把「谁会受影响」变成实验里一个可以填的格子。
没有一种新药是直接上市给所有人吃的。先在很少几个人身上试,看安全;再扩到几十、几百人;而且每一期都事先写好「出现什么情况立刻停」,还有人专门盯着这条线。
在真人身上做混沌实验,规矩是同样三条:从极小的人群开始、一级一级放大、事先定好自动叫停的条件。只有一点不同:软件的「停药」能在几秒内完成,所以你可以比医学大胆一点。
最常见的做法是随机关掉一台机器。它的毛病不是风险大,而是——你没法选谁倒霉。
落在那台机器上的是谁,取决于流量当时怎么分。可能全是随手刷刷的人,也可能包含一个正点下「提交申请」的人。「只影响千分之一的请求」听着很小,但那千分之一不是数字,是某些具体的人的某一次具体操作。
还有更隐蔽的一层:如果系统总把同一个人分到同一台机器,那「影响千分之一的请求」其实是「让千分之一的人全程崩坏」。一个人连失败一百次,和一百个人各失败一次,报表上是同一个数,体验上是两件事——前者会卸载 App。
好处是实验从「要不要冒这个风险」变成了「冒多大」:绝大多数毛病在「摇自己」那一级就露出来了,根本用不到真实用户;真需要放量,也是一级一级放、随时能收回。
一句诚实的代价:精确到一次请求的故障是「装」出来的——它只让这一次请求以为下游坏了,而真故障会同时砸在所有调用方身上,那种全局拥挤只有放到规模上才看得见。
生产混沌的爆炸半径该用「人」做单位:把故障挂在一次请求上随它传播,先摇自己、再按人群一级一级放量,并且事先写好会自动触发的叫停条件——做不到自动叫停的实验,就不该在真人身上跑。
想进到具体机制、量级和示意图? → 切到精读版
你以为「最小化爆炸半径」是把实验调小——少关几台机器、少放一点流量。这一章换了单位:爆炸半径的单位是人,不是机器。LinkedIn 有数亿会员,而其中一次会员体验可能正好是一场面试邀约;所以「关一台机器」的根本毛病不在风险大,而在你无权选择谁受伤。它的工程答案是把注入粒度压到一次请求:故障指令随请求沿调用链向下传播(LinkedOut),受影响集合从「谁碰巧被路由到」变成实验的显式输入;再用「先摇自己 → 内部员工 → 小队列 → 集群级」的阶梯逐级换信息,每一级都配一条跑得比伤害更快的自动中止。
全书第 7 章,Part II「原则落地」的第四篇,视角来自 LinkedIn。前三篇分别管「怎么演」「演多大」「先演哪个」:Ch4 Slack 给存量系统写剧本、Ch5 Google 把 DiRT 放大到公司规模、Ch6 微软解决排序;这一篇管演在谁身上,把 Ch3 第五条原则「最小化爆炸半径」从口号做成机制。往后 Ch8 Capital One 是同一问题的强监管版本,Ch16 持续验证把本章的放量与判定自动化。现实里它对着任何一个「工具已经能注入、但不敢在生产按下按钮」的团队。
把约束摆出来:本书写作时 LinkedIn 公开口径的会员数在六七亿量级,而它的特殊之处是单次体验的权重极不均匀——同一分钟里有人随手刷信息流,也有人在点「提交申请」、在和招聘方对话。前者卡一下无所谓,后者一次失败可能真改变一件事。
再看「随机关一台机器」。1000 实例的服务关 1 台 ≈ 影响 0.1% 的请求;按机器算这叫「小」,按人算还欠两个问题:这 0.1% 落在谁身上、他们当时在做什么——而杀实例这种做法根本不回答,答案由负载均衡那一刻的分配决定。
更难受的是粘性路由把数字放大的那一下:如果路由按会员哈希,「1% 的请求」其实等于「1% 会员的全部请求」。一个人连着失败一百次,和一百个人各失败一次,在可用率仪表盘上是同一个数值,在真实体验上一个可忍一个是事故。
不解决会怎样?两种退化:不敢做——工具和权限都有,但没人敢按按钮,于是混沌停在既无真实负载也无真实依赖的预发环境;或者硬做然后被叫停——摇出一次真事故,之后半年不许再提。本章的分寸恰恰是为了保住长期做实验的许可。
所以这章的问题是:怎么把「谁受影响」从一个赌注变成一个可填的参数?
本章最该带走的那次口径更换:别问「影响几台机器、几个百分点」,问影响多少人、哪些人、他们当时在做什么、持续多久、可不可逆。
后三个最常被漏。持续多久决定一个人是遇到一次重试还是五分钟白屏;可不可逆把「页面少一块」和「申请提交丢了、消息发出去一半」分成两类——后者再小的比例都不该拿来做实验。这也解释了为什么摇读路径和摇写路径在本章里不是同一档风险。
换完口径,实验设计的方向立刻变了:目标不是把规模调小,而是让「受影响集合」成为一个你能写下来的输入。杀实例做不到这件事——它的受影响集合是运行时才知道的。
LinkedIn 的框架叫 LinkedOut,一句话概括它的位置:注入点是 Rest.li 栈里的一个请求过滤器,而「这一跳该不该注入」由随请求传下来的上下文(Invocation Context)决定。
它能注三种形态:error(抛出异常,模拟资源不可用)、delay(转发前加一段延迟)、timeout(到点抛超时)。触发方式有两类:用 LiX 把实验定向到一群会员,或者把指令放进一个 cookie 里——于是粒度可以细到「我这一次打开这个页面」。
为什么不容易:一个页面扇出几十个下游,故障要只落在「这一个会员的这一次请求打到服务 X 的那一跳」,既不漏到别的会员,也不漏到服务 X 的其他调用方——这要求框架每一跳都把上下文完整传下去。反过来就是它的隐含代价:没接这套传播机制的路径注不进去(第三方 SDK、离线任务、消息队列后面的消费者)。
换来的是爆炸半径的量级跳变:从「那台机器上所有会员在重启窗口内的所有在途请求」,降到「一个人的一次点击」。
一旦注入能精确到一次请求,最便宜的实验对象就是工程师自己。LinkedOut 为此配了一个浏览器扩展:工程师在自己的会话里点一下,让页面上某个下游「坏掉」,直接看页面还能不能用。爆炸半径是一个知情同意的人。
再往上一级是自动化套件:把一个页面的每个下游依次坏一遍,逐个检查是否优雅降级。这类实验之所以敢全自动、高频跑,正因为受影响的只有发起它的那个账号——它的边际伤害是零,于是可以当测试用。
阶梯的全部意义是用最便宜的一级榨出最多的问题:缺超时、缺降级、下游一挂就整页白屏,这些在第一级就暴露了。真正值得放量的,只是「必须有真实流量才看得见」的那一类。
也要诚实说上限:有些故障天生只在规模下出现——连接池饱和、重试风暴、缓存集体失效、下游被所有调用方同时打爆。单请求注入里看不见它们,必须放量或改用集群级实验。这就是阶梯的信息天花板。
放量到真实会员之后,「有没有伤到人」这个判断不能挂在人盯屏幕上。三条要点:
一条可以直接拿去用的判据:没有自动中止条件的实验,不该在真人身上跑。这不是道德姿态,是成本算术——「人工发现 + 人工处置」的时延乘以放量人群,就是你为这次实验预付的伤害。
第一张表是阶梯的落地:每一级换到什么信息、付什么代价。
| 放量级别 | 爆炸半径(人) | 能验到 | 验不到 | 中止手段 |
|---|---|---|---|---|
| ① 工程师自己的会话 | 1(知情同意) | 降级路径、超时是否存在、错误页面 | 一切规模相关的故障 | 关掉扩展即止 |
| ② 内部员工 / dogfood | 10²–10³ | 跨页面、跨端的降级一致性 | 真实用户的行为分布 | 关放量开关 |
| ③ 定向会员队列 | 10⁴ 量级起 | 真实流量下的指标偏移、告警是否响 | 全站级拥挤 | 指标越线自动回收 |
| ④ 集群级 / 整区域 | 10⁶+ | 池饱和、重试风暴、容量与切流 | — | 预案 + 人在回路 |
第 ① 级的性价比来自它的边际伤害为零:可以当测试一样高频跑。
第二张表是同一件事的另一刀:限制伤害的几种手段,在保真度上各付什么代价。
| 手段 | 对会员的伤害 | 保真度 | 验不到什么 | 适用 |
|---|---|---|---|---|
| 请求级定向注入 | 极低,可精确到一人一次 | 中:模拟的是「下游坏了」的观感 | 所有调用方同时受影响的全局效应 | 日常高频,验降级 |
| 分流到实验集群(对照组) | 低,按比例 | 高:真实流量、真实依赖 | 需要架构支持分流 | 放量判定、持续验证 |
| 预发 / 影子环境 | 零 | 低:负载与数据都不真 | 规模行为与真实依赖 | 高危形态的首演 |
| 生产直接关实例 / 断区域 | 高,且无法选谁 | 最高 | — | 容量与切流演练,要预案与窗口 |
落到具体选择:
三句能直接改掉评审结论的话。一:爆炸半径的单位是人,不是机器。二:「只影响 1% 流量」在粘性路由下等于「1% 的人全程受影响」。三:没有自动中止条件的实验,不该在真人身上跑。
面试被问「你们怎么在生产做混沌工程」:答「半夜随机关机器」是一个层级;答「注入做到请求级、默认先在发起人自己的会话上跑,放量走定向队列并绑定会员可感指标的自动中止」是另一个层级——后者显示你知道难点在谁受影响,不在能不能注入。
大厂实证
randomly terminates:这种做法不回答「受影响的是谁」。本章不是说它没用(它是起点),而是说粒度决定了你能不能为伤害负责。Netflix · Chaos Monkey 官方仓库 README(github.com/Netflix/chaosmonkey,英文原文经本次运行核实)0.1%,落在刷信息流的人身上和落在正在提交申请的人身上,是两件不同的事。① 核心命题:爆炸半径的单位是人,不是机器。「关一台机器」的根本毛病不是风险大,而是你无权选择谁受伤。
② 换口径:别问影响几台、几个百分点,问影响多少人、哪些人、在做什么、持续多久、可不可逆——后两问最常被漏。
③ 机制:故障指令随请求沿调用链传播。LinkedOut = Rest.li 请求过滤器 + Invocation Context,三种形态 error / delay / timeout,用 LiX 定向人群或 cookie 精确到一次打开。
④ 它的工程价值只有一句:把受影响集合从「谁碰巧被路由到」变成实验的显式输入。
⑤ 阶梯:自己的会话(1 人)→ 内部员工(10²–10³)→ 定向会员队列(10⁴ 起)→ 集群级(10⁶+)。绝大多数问题死在第一级,这是敢在生产做的前提。
⑥ 粘性路由会把「影响 1% 请求」放大成「1% 的人全程受影响」——仪表盘上同一个数,体验上两件事。要按会员定向并限制命中时长。
⑦ 自动中止三条:会员可感的业务指标、伤害在本级时间尺度内可见、秒级自动生效。判据:没有自动中止条件的实验不该在真人身上跑。
⑧ 诚实的上限:请求级注入看不见全局效应(池饱和、重试风暴),它与集群级实验是分工而非取代;而「做得克制」真正买到的东西,是长期还能继续做实验的许可。