专业书籍精读 · SRE · 第 12 章
Site Reliability Engineering · Ch 12 · Chris Jones · Google · 2016
网站打不开、App 转圈、付款卡住——总得有人把毛病找出来。你可能以为这活儿靠天赋:有些人天生「手感好」,扫一眼日志就知道哪儿坏了。Google 这本书的第 12 章说:不是天赋,是手艺,手艺可以教。它把「高手怎么查故障」拆成了一套谁都能照着走的步骤。
把它想成急诊室。病人推进来,医生的第一件事不是弄清病因——是先把血止住。原因可以之后慢慢查,人不能等。稳住之后才是抽血、拍片(看指标和日志),然后推断,然后试着用药看反应,最后写病历。
整章最值钱的一句就藏在这个顺序里:第一步不是找原因,是止血。你花四十分钟查清了真凶,这四十分钟里用户一直在疼——他们不会因为你破了案就少疼一分钟。
难在「猜」。书里点了几种最常见的猜法:盯着一个跟这事没关系的指标看半天;把巧合当因果——两条线一起往上翘就认定一个导致了另一个(监控的东西越多,碰巧长得像的两条线就越多);还有抓住上次的原因不放。
① 止血和破案是两件事,可以同时干。把流量切到没坏的机器上、关掉一个不重要的功能、把慢的东西降级——这些都不需要知道原因。但有个前提:别把现场清了。最常见的止血手段是重启,而重启往往把唯一的证据一起烧掉,于是同一个故障下个月照样再来。
② 折半,而不是从头一个个试。一次请求要过七八个环节:网关、前端、鉴权、业务、缓存、数据库、存储。直接在中间那个环节量一刀:中间是好的,毛病在后半段;中间已经不对,毛病在前半段。一刀砍掉一半,七八个环节三刀之内就能圈定。这是「查」和「猜」的分水岭。
③ 坏掉的系统通常还在忙,只是忙错了地方。所以别问「它为什么坏」,问三句更具体的:它在干什么?力气花在哪儿?为什么花在那儿?书里的例子:一个数据库集群变慢,问「CPU 用在哪」,发现在给日志排序;再问「排序里的哪一步」,发现是拿一条写得很糟的匹配规则去套文件名。症状和真凶隔着四层,靠猜猜不到,一层层追问「力气花在哪」就必然能到。
④ 最省事的一问:最近谁动过它?系统有惯性——好端端跑着的东西不会自己坏。先翻改动记录,往往比翻代码快得多。
2019 年 7 月,Cloudflare 上线了一条新的防火墙规则,里面一段匹配写得不好,让全球机器 CPU 瞬间打满,大量网站打不开。看时间线就懂了这章:13:42 出事,14:02 才弄明白怎么回事,明白的当下就一键把这批规则全局关掉,14:09 流量恢复。至于那条规则本身怎么改、怎么重新打开,是四十多分钟以后的事。
排障是手艺不是天赋:先止血再破案(但别把证据一起清掉),从中间折半而不是从头挨个试,问「它在忙什么、力气花在哪」而不是干瞪着「它为什么坏」,还有永远先看最近谁动过它。诚实说一句代价:这套流程全建立在「你能看见系统里发生了什么」之上——日志、指标、请求链路都得在平时花钱埋好,当初没埋,出事那天流程再漂亮也只能靠猜。
想进到完整流程图、对比表和真实案例? → 切到精读版
SRE 第 12 章要拆掉一个流行误解:排障(troubleshooting)不是某些人天生的直觉,而是一门可分解、可传授的手艺。它等于两样东西相乘——一套通用的假设-演绎流程(问题报告 → 分诊 → 观察 → 诊断 → 测试 → 治愈),乘以对这个系统的具体了解;缺了流程,你会瞎猜,缺了系统知识,你连能被检验的假设都提不出来。最反直觉的一课是顺序:第一步不是找原因,是止血——「你正在找根因的时候系统死了,对用户没有任何帮助。」
n 个候选大约 log₂n 次就能定位——100 个候选约 7 次。作者 Chris Jones。本章是 SRE 书 Part III「实践」里的核心技能章:上承第 6 章「监控分布式系统」(症状告警负责把问题送到你面前)与第 11 章「值班」(谁去接),下启第 13 章「紧急响应」、第 14 章「事故管理」与第 15 章「复盘文化」。它也是第 9 章「简单性」的直接受益方——你能排的障,不会超出你还理解得了的系统。放到现实里,它对应的是值班手册、事故指挥、可观测性平台选型,以及那道最常见的面试题:「线上 500 错误率突然涨到 3%,你怎么查?」
本章开篇挑明的问题是:排障常被当成一种天赋——有人有,有人没有。之所以有这种错觉,是因为对天天干这事的人来说,这套流程已经内化到说不清了。把它当天赋的代价很实在:不教、不练、不写下来,团队的排障能力就绑在几个老人身上,人一走就归零。
第二个问题是分布式系统让老经验失效。单机时代的调试是「找到坏掉的那台机器 / 那个进程」;而在一次请求扇出到几十上百个 RPC 的系统里,往往没有任何一台机器是坏的——只是某一跳慢了 300 毫秒,然后超时、重试、排队,最终在最外层表现为「网站慢」。你要找的不是坏零件,是一条路径上的一段异常。
不解决会怎样?把账算给 SLO 看最直接:一条 99.9% 的月度可用性目标,整月只允许约 43 分钟不可用(第 3、4 章的口径)。也就是说排障时长不是软指标,它就是可用性的分母——同一次故障,30 分钟定位和 5 分钟定位,差的那 25 分钟就吃掉当月大半个错误预算。
本章还先教「不要做什么」,列了四种无效排障的典型病:① 盯着不相干的症状,或误读了某个指标的含义,一路缘木求鱼;② 不清楚怎么安全地改动系统 / 它的输入 / 它的环境,因而没法验证自己的假设;③ 提出极不可能的理论,或死抓住上一次故障的原因不放;④ 追逐虚假相关——纯属巧合,或两者其实同源于第三个原因。第四条随规模自动恶化:一个服务导出几千个指标,两两配对就是几百万对,总会有几对曲线长得一模一样。而这里还有一条本书引论给过的经验值:约 70% 的线上故障源自对运行中系统的变更——这既是坏消息,也是最好的第一线索。
本章给排障下的定义是假设-演绎法的一次应用:手上有一组关于系统的观察,加上一套「系统本该怎么运转」的理论,于是反复地提出可能的原因,然后想办法检验它。注意这里的两个输入缺一不可。只靠通用流程也能干活,但通常远不如懂这个系统的人有效率;反过来,只有系统知识、没有流程,人就会跟着直觉乱撞,而直觉恰恰是四种无效排障的温床。
这也解释了为什么排障「看起来」像天赋——老手的系统知识是隐性的、流程是内化的,两样都说不出口,外人只看见「他一眼就看出来了」。本章的全部工作就是把这两样摊到桌面上:流程写成图 1,系统知识落成值班手册与「可观测性要设计进去」。
这一节是全章最该背下来的。本章的原话是:「止血应当是你的第一优先级;你正在找根因的时候系统死了,对用户没有任何帮助。」分诊的目标被定义成一句朴素的话——让系统在当前条件下尽可能地正常工作。手段是应急性的,不需要知道原因:把流量从坏掉的集群切走、主动丢弃一部分负载保住其余、把功能降级成返回缓存或简化结果。
这两件事根本不该串行:成熟系统里一次跨集群切流量是分钟级操作,而定位一个偶发的内存泄漏可能要几小时。让用户为后者等着是纯粹的浪费。
但本章紧接着补了一句最常被忘掉的约束:「强调快速分诊,并不排除采取措施保存出问题的证据(比如日志),以便后续做根因分析。」它的现实含义很刺眼——最常用的止血手段(重启 / 换机器)同时也是最有效的毁证手段:内存状态、当时的堆栈、只在故障期间出现的日志,重启之后全没了,于是同一个故障每月准时回访、每次都靠重启「解决」。可操作的做法很便宜:留一个摘出流量但不杀掉的实例、先抓一份转储再重启、把这段时间的日志单独归档。
「观察」这一步要做的是逐个组件地看它自己的行为,判断整体是否健康。本章给的工具是三层:
诊断的第一把刀是把搜索空间砍小。在一条由多个组件串起来的链路上,不要从第一个开始挨个查,而是在中间下刀:如果中间那一点的输入输出都正常,故障在下游;如果中间已经不对,故障在上游。每一刀砍掉一半,n 个环节大约 log₂n 刀收敛——7 跳约 3 刀,100 个候选版本约 7 刀。同一把刀也能横着用在时间轴上:在 100 个提交里二分定位是哪次改动引入的回归。
第二把刀基于一个观察:坏掉的系统通常还在努力做事,只是没在做你想要的事。所以别问那个太大的问题「它为什么坏了」,改问三个小的:它在做什么?它的资源花在哪儿 / 输出去了哪儿?为什么花在那儿?每回答一层,就往下钻一层。本章给的例子把这套下钻演示得极干净:
第三把刀最省事,也最常被跳过。本章的说法带点物理味道:系统是有惯性的——一个正在正常运转的计算机系统会保持运转,直到受到某种外力作用,比如一次配置变更,或者流量性质的变化。所以「最近的变更」永远是第一顺位的嫌疑人,回滚则是最快的一次假设检验:退回去好了,基本就是它。
要让这把刀好用,有个前置条件:系统各层都得有变更记录——从处理用户流量的服务二进制版本、配置、实验与旗标,一直到集群里每台机器上装的软件包。没有这份记录,「最近动过什么」就变成了群聊里的口头考古。
有了假设就要检验。本章列的注意事项,条条都是血泪:
把这条反过来说更醒目:排障中最贵的开销往往不是测试本身,而是忘了自己已经测过什么。
严格证明因果需要对照实验,线上通常做不到,于是现实中常常止步于相关性加上一个说得通的机制——本章对此是坦诚的。提高把握的做法有两条:在测试环境里复现,以及把整个链条写下来,即一份说清「出了什么问题、怎么定位、怎么修、以后怎么防」的复盘(本章顺口开了个玩笑:但愿写的时候系统还活着)。
本章还专门用一节讲了一件容易被扔掉的东西:负面结果是魔法(negative results are magic)——「我们试了 X,它不是原因 / 它没有变快」。主张有四层:① 负面结果是结论,不是失败,一个干净的否定能一次解决掉最难的设计争论;② 为这次实验造的工具和方法,寿命通常比实验本身长;③ 不发表,下一个人(很可能是三个月后的你自己)会把同样的坑再踩一遍;④ 所以——请发表负面结果。这针对的是行业里一个真实的偏见:没人愿意写「我们试了这个,没用」。
表 1 · 止血(分诊)vs 破案(诊断):两件事,两个时钟
| 止血 · 分诊 | 破案 · 诊断 | |
|---|---|---|
| 目标 | 让系统在当前条件下尽可能正常工作 | 找出是什么导致了它 |
| 典型动作 | 切流量 / 丢弃负载 / 降级 / 回滚 | 看指标与日志 / 追踪 / profile / 折半 |
| 需要知道原因吗 | 不需要 | 这就是它的产出 |
| 时间尺度 | 分钟级(成熟系统里切流量常是一条命令) | 几十分钟到几小时,且不可预测 |
| 主要代价 | 可能毁掉证据;降级期间用户体验打折 | 期间用户一直在承受故障 |
| 该怎么排 | 并行,不是串行。先止血,同时留一个摘出流量但不重启的实例、抓好转储与日志,再慢慢查。 | |
表 2 · 四把诊断刀:什么时候用哪把
| 手法 | 最擅长的故障 | 前提条件 | 失效的时候 |
|---|---|---|---|
| 折半 / bisection | 链路上「某一跳坏了」、某次提交引入的回归 | 每一跳能被单独探测;故障可稳定重现 | 偶发、间歇的故障——一刀下去它恰好没犯 |
| 什么 / 在哪 / 为什么 | 资源被吃光类(CPU、内存、句柄、连接) | 有 profile、有分层的耗时数据 | 系统完全不动了(没在「忙错地方」,是根本没忙) |
| 最近谁动过它 | 约 70% 的故障——跟在一次变更后面的那种 | 各层都有变更记录(版本、配置、旗标、软件包) | 剩下 30%:负载性质变化、证书到期、容量到线、上游变更 |
| 专用诊断工具 | 本服务特有的、通用工具看不见的状态 | 有人愿意平时投入去建 | 只有作者会用;没写进值班手册就等于不存在 |
表 3 · 观察手段:各自回答什么问题、盲区在哪
| 手段 | 回答的问题 | 常态开销 | 盲区 |
|---|---|---|---|
| 白盒指标 | 哪个组件不对劲、什么时候开始的 | 低,可常年全量 | 只到组件粒度;指标越多,虚假相关越多 |
| 文本日志 | 这一刻具体发生了什么 | 中;verbose 会反过来拖慢系统 | 没埋的地方就是黑的;量大时难聚合 |
| 分布式追踪 | 一次请求的时间花在第几跳 | 低(靠采样压下来) | 采样率低时抓不到罕见的坏请求 |
| profile | CPU / 内存花在哪个函数 | 按需开启为主 | 只看得见「在忙」,看不见「在等」 |
表 4 · 验证假设时,测试怎么排序(低风险在前)
| 类型 | 例子 | 信息量 | 风险 / 副作用 |
|---|---|---|---|
| 只读观察 | 看仪表盘、查变更记录、读一条 trace | 中 | 几乎为零 —— 永远先做完这一档 |
| 旁路复现 | 在测试环境重放请求、单独打一台副本 | 高 | 低;但可能复现不出来(环境不一致) |
| 可逆的线上改动 | 回滚一个版本、关掉一个旗标、调一台机器的参数 | 很高(回滚好了基本就是它) | 中;先在一小部分流量上做 |
| 侵入式动作 | 开 verbose 日志、抓堆转储、重启主副本 | 高 | 会改变后续结果:verbose 本身拖慢延迟,重启抹掉现场 |
这一章的四条判据,几乎每一条都能在公开的事故复盘里找到正反两面的印证。落到日常:值班时先问「切得走吗、降得了吗」再问「为什么」;事故复盘里检查有没有为了止血把证据一起清掉;可观测性选型时先确认「一次请求的耗时能不能拆到每一跳」;面试里遇到「错误率突然涨到 3%,你怎么查」,标准答卷就是这套顺序——先止血、看最近变更、折半缩范围、逐层问力气花在哪。
merge_join 调用相关——这类调用通常意味着索引不理想;随后用 Dapper从前端反向代理一路追到应用返回响应,逐跳检查每个服务发出的 RPC。方法论上的要点正是图 2:从「整体变差了」降到「第几跳变差了」。SRE Book Ch.12 Effective Troubleshooting(全文免费) ↗pg_dump 因版本不匹配长期静默失败),最终靠一份来自 staging 的快照恢复,丢失约 6 小时数据。GitLab 把整个恢复过程公开直播并写成复盘——这正是本章「请发表负面结果」的一次代价高昂的示范。Postmortem of database outage of January 31 ↗① 一句话:排障不是天赋,是可分解、可教的手艺 = 通用的假设-演绎流程 × 对这个系统的具体了解,两者缺一不可。
② 流程是一个环:问题报告 → 分诊 → 观察 → 诊断 → 测试/处置 → 治愈,假设被证伪就退回「观察」再提一个。
③ 最该背下来的顺序:止血优先——「你正在找根因的时候系统死了,对用户没有任何帮助」。分诊与诊断并行,不串行。
④ 但快速分诊不等于允许毁证:重启是最好的止血也是最好的毁证,务必留一个摘流量但不重启的实例、先抓转储与日志。
⑤ 先学会四种无效排障:看错症状 / 误读指标、没法验证假设、死抓上次的原因、追逐虚假相关(指标越多,巧合越多)。
⑥ 诊断四把刀:折半(log₂n 刀,7 跳约 3 刀)、问什么/在哪/为什么(Spanner → CPU → 日志排序 → 一个会回溯的正则,隔了四层)、最近谁动过它(约 70% 的故障跟在变更后面,回滚是最快的一次假设检验)、专用工具。
⑦ 测试要让备选假设互斥、按「可能性 ÷ 风险」排序、警惕主动测试的副作用(verbose 日志本身会加剧延迟),并且把试过什么写下来——最贵的开销是忘了自己测过什么。
⑧ 现实中常止于「相关 + 说得通的机制」;负面结果是魔法:它是结论不是失败,工具能活得比实验久,不发表就等着别人(和三个月后的你)重踩——所以请发表。
⑨ 观察分三层:指标说哪个组件不对、日志说那一刻发生了什么、追踪说时间花在第几跳。而本章的收尾主张最值钱:排障容不容易,是在设计那天决定的——把可观测性建进每个组件、让组件之间的接口清晰且可观察,比事后任何技巧都管用。