专业书籍精读 · Google SRE · 第 17 章
Site Reliability Engineering · Ch 17 · Google · 2016
你手机里的 App 几乎每周都更新,淘宝、微信背后的服务甚至一天要改几十次。奇怪的是,它们很少「一更新就全崩」。Google 的《SRE》第 17 章讲的就是这件事:改动是躲不掉的,那怎么让改动别把系统弄坏?答案是一整套「先尝一口再端上桌」的规矩。
把上线新版本想成餐厅上新菜。菜谱改了,你不会直接端给满堂客人。你会:先在后厨自己尝一口(咸不咸);再把整道菜连摆盘带上菜流程走一遍;然后先只端给靠窗那一桌,看他们吃完什么反应;确认没事才全店推。而且不管有没有上新菜,你每天都会派个自己人假装点一份,确认厨房还活着。这几步,正好对应这一章讲的几种检查。
不改代码最安全,但也等于不做生意。真正的难处是:翻车往往不在「改错了一行」,而在只有生产环境才有的那些东西——真实的用户流量、真实的配置、真实的机器数量。后厨里怎么尝都好好的,一端进大堂就乱套。更糟的是,很多团队上线之后只剩一招:盯着监控祈祷,等用户替自己先发现问题。
这一章最反直觉的一句是:测试真正的价值不在「找到毛病」,而在决定这个毛病会在哪一步被找到。同一道菜咸了,在后厨尝出来,代价是重做一份;端上桌被客人吃出来,代价是道歉、退单、丢口碑。软件一模一样:被自动检查拦在上线之前,修复几乎不花钱;漏到线上,就是半夜告警、排查、回滚、写复盘,一晚上没了。
所以它教的不是「多写点测试」,而是把关卡摆在代价最低的那个位置,并且认清每道关各自漏什么——后厨尝不出大堂的拥挤,靠窗那一桌也代表不了所有客人。
小系统、出事一分钟能撤回的,关卡可以松一点,靠「不对就立刻回退」;大系统、一旦出事要折腾几个小时的,就必须把关卡往前挪、在上线之前拦住。代价也很诚实:关卡越多发布越慢,而且一个「时灵时不灵」的检查比没有检查更糟——它会教会所有人无视红灯。
测试不是为了证明「没毛病」,是为了让毛病在最便宜的地方被发现。上线前尝一口、上线后先给一小桌、平时还派人假装点单——三道关各管一段,缺一不可。
想进到具体机制、示意图和真实系统? → 切到精读版
你以为测试是为了找出 bug,这一章的主张更锋利:测试是 SRE 用来「量化信心」的工具,它真正改变的不是 bug 的数量,而是 bug 的修复代价。原文开篇即说,SRE 的一项核心职责是量化对所维护系统的信心;而可靠性是平均无故障时间(MTTF)与平均修复时间(MTTR)的函数。测试的经济学就落在 MTTR 那一边——被测试卡在发布之前的 bug,修复时间近似为零。
本章属全书 Part III「实践」,紧跟第 15 章「事后复盘文化」那一组故障处理之后,上承第 3 章「拥抱风险」的错误预算与第 8 章「发布工程」,下启第 19–22 章的负载均衡与过载防线、第 27 章的发布协调。它对应现实里的那条链路:CI 流水线 → 灰度发布 → 黑盒探测——今天被叫作渐进式交付(progressive delivery)与合成监控(synthetic monitoring)。
SRE 要回答一个每天都被问的问题:「这个版本能上吗?」过去的可靠性可以靠监控历史数据说清楚,未来的可靠性却只能靠预测。而预测要成立,前提是系统没变——可系统天天在变。这一章给出的解法是:测试就是在变化发生时,证明「哪些地方仍然等价」的机制;每一个在改动前后都通过的测试,就消掉一块不确定性。
不解决会怎样?两种极端都见过。一种是不敢发:每次上线拉全组人手动验,节奏掉到一月一次,功能积压成巨型变更、出事更难定位。另一种是盲发:推上去盯监控,让用户当探测器——线上一个 bug 的完整代价是「告警 → 定位 → 回滚 → 复盘」,动辄几小时;而同一个 bug 被 CI 拦下,代价只是一次红色构建加一次提交。
原文有一个说法值得抄下来:当一个系统级测试正好抓到了「监控本来会抓到的那个问题」,这次推送就能被直接卡住,bug 根本到不了生产——这种 bug 的 MTTR 是零,靠拦住一次发布来修复它,既快又省事。
这句话把测试从「质量活动」重新定义成经济活动:你不是在消灭 bug(消灭不完),而是在决定它被发现的位置,位置决定代价。
第一类测试在未部署的二进制上跑,喂的是构造好的已知数据。三层:
回归测试有一条极具操作性的判据:发现 bug 时,先写一个能稳定复现它的测试,再动手修。它顺带回答了「接手一个没有测试的老项目该从哪下手」——不必先补全量覆盖,从每一个你修过的 bug 开始攒。
第二类测试跑在已经部署的系统上,因为有些事情只有生产环境才有。
plan 与漂移检测(drift detection)就是它的当代形态。关于金丝雀,原书给了一句最常被忽略的话:它其实不是测试,而是「有组织的用户验收」。配置测试与压力测试是在确定性的软件上确认某个条件成立;金丝雀则更即兴——它只把待测代码暴露给一小片不可预测的真实流量,因此并不完美,也不总能抓到新引入的故障。把它当最后一道保险可以,当成「验过了」不行。
把前面的东西摆成一张两维表,会看到一个常被漏掉的格子。传统测试 = 已知输入 × 未部署的二进制;监控 = 未知的真实流量 × 已部署的二进制。那么「已知输入 × 已部署的二进制」归谁管?——归探针。
原书点破了这块空白的成因:发布测试跑的那套组合,往往是「集成好的服务 + 一个前端 + 一个假后端」;而探针跑的是「发布二进制 + 负载均衡前端 + 真正可扩展的持久化后端」。换句话说,生产里真正在跑的那个组合,此前从未被完整测过。Google 用一个叫 Prober 的工具补这一格:它对目标跑一次协议级检查、报告成功或失败,还能校验响应负载本身(比如 HTTP 返回的 HTML 内容是否符合预期),并把提取出的值导出成时序指标。
探针的代价也要说清:它对生产发的是真实请求,要处理写入副作用(通常用专门的测试账号与可回收数据)、配额消耗,以及「探针自己挂了算谁的」——它的告警必须能区分「服务坏了」和「探针坏了」。
① SRE 自己写的工具也是软件。排空集群流量、批量重启、回收数据、调配额——这些工具的爆炸半径往往比业务代码还大,却常常一行测试都没有。原书给了一条很利落的判据:测试的充分程度,决定了这个工具多久可以对生产用一次。没测过的工具就只能在人盯着的窗口里手动跑;测充分了才配得上自动化。
② 灾难恢复工具怎么测?它天然只在最糟的时刻被用到,没法等真出事再验。原书的做法很巧:很多灾难恢复工具可以被设计成离线运行,算出一个「等价于把服务干净地停掉」的检查点状态,再把这个状态推给现有的、非灾难场景下的校验工具去检查。把一次灾难变成一份可以离线校验的快照——这是全章最值得抄走的一招。
③ 速度就是可用性。测试跑得越久,从签入到拿到反馈的时延越长,工程师就越倾向于绕过它。而不稳定测试是更隐蔽的毒:工程师真正想知道的是「我这段代码是不是有没预料到的竞态」,如果测试本身时红时绿,这个信号就被噪声淹没了——一个偶发失败的测试比没有测试更糟,它会训练整个团队无视红灯。
最后,这三件事被同一个控制回路串起来:MTBF 随测试变好而上升,开发者就被鼓励发得更快;新 bug 出现,发布速度又被压回去。落到实践就是错误预算——SLO 还有余量就继续发,预算烧完就停发、把资源投回测试与稳定性。
表 1 · 七种检查各守哪一段(抓什么 / 漏什么)
| 检查 | 在哪跑 | 抓得住 | 抓不住 / 代价 |
|---|---|---|---|
| 单元测试 | 未部署,毫秒级 | 单个函数 / 类的逻辑错误 | 组件之间的交互一概看不见 |
| 集成测试 | 未部署,秒级 | 组件拼起来之后的契约错误 | 依赖被 mock 掉,真实下游的行为差异漏掉 |
| 系统测试 | 未部署完整系统 | 端到端行为、性能漂移、老 bug 复发 | 跑得慢;仍是假数据、假拓扑 |
| 配置测试 | 生产,持续 | 线上实际配置与版本库不一致 | 配置「对」但语义错了,仍然测不出来 |
| 压力测试 | 生产或准生产 | 容量与降级边界(写满、过载、磁盘) | 要占真实资源;做过头会自己造一次事故 |
| 金丝雀 | 生产,1% 流量 | 只有真实流量才暴露的问题 | 原书明言:它不是测试,不总能抓到新故障;要付出烘焙时间 |
| 探针 | 生产,7×24 | 「线上这套组合」现在还活着吗 | 只覆盖被显式断言的少数路径;有写入副作用 |
表 2 · 钱该往哪边投:先看你的 MTTR
| 把钱投进「上线前拦住」 | 把钱投进「上线后快速撤回」 | |
|---|---|---|
| 适合 | 回滚要几十分钟到几小时:有状态服务、数据迁移、schema 变更、嵌入式 / 客户端发版 | 回滚以秒 / 分钟计:无状态服务、功能开关、可即时切流的前端 |
| 核心手段 | 更厚的测试金字塔、配置测试、发布前压测 | 金丝雀 + 自动判读、功能开关、一键回滚 |
| 代价 | 发布节奏被测试时长钉死;测试套件本身要维护 | 总有一小撮真实用户当探测器;需要成熟的监控与自动化 |
| 失败方式 | 测试太慢 → 被绕过;不稳定 → 被无视 | 金丝雀信号太弱 / 观察期太短 → 放大后才爆 |
两边不是二选一,而是同一个方程的两侧:可靠性是 MTTF 与 MTTR 的函数,你可以从任一侧买到它。真正的错误是两侧都不投,然后指望运气。
今天几乎所有「现代交付」的做法,都能在这一章里找到原型:CI 上的分层测试对应 Bazel / GitHub Actions 那套流水线;配置测试对应 Terraform 的 plan 与漂移检测;金丝雀对应 Spinnaker、Argo Rollouts、Flagger 的渐进式发布;探针对应 Prometheus 的 blackbox_exporter、Datadog Synthetics 与各家的拨测。读懂这一章,等于拿到了这些工具「为什么长成这样」的说明书——也是被问到「你怎么保证一次发布是安全的」时,能从 MTTR 讲起而不是背工具名的底气。
420 万 个测试持续运行、每天约 1.5 亿 次测试执行(平均每个测试每天跑 35 次),其中 99% 的执行是通过的——这组数字正是「速度就是可用性」的硬证据:在这个体量下,测试时长与不稳定率直接决定工程师多久能拿到反馈。A. Memon 等《Taming Google-Scale Continuous Testing》, ICSE-SEIP 2017 ↗30% 的生产金丝雀判定、日均约 200 次——印证了原书「金丝雀是用户验收而非测试」这一定位:它需要统计判读,而不是一个断言。Netflix Tech Blog「Automated Canary Analysis at Netflix with Kayenta」, 2018-04 ↗① 一句话:测试是 SRE 用来量化信心的工具;它改变的不是 bug 的数量,而是 bug 被发现的位置,从而改变修复代价。
② 核心命题:可靠性是 MTTF 与 MTTR 的函数;被测试卡在发布之前的 bug,MTTR 近似为零,靠拦一次推送来修既快又省事。
③ 传统测试(未部署 + 已知数据):单元 → 集成 → 系统,系统测试再分冒烟 / 性能 / 回归。
④ 生产测试(已部署):配置测试(线上配置与版本库比对)、压力测试(主动找容量边界)、金丝雀。
⑤ 原书明言:金丝雀不是测试,是有组织的用户验收——只暴露给一小片不可预测的真实流量,不总能抓到新故障。
⑥ 被漏掉的那一格是探针:已知请求打已部署的生产系统。理由是「线上这套真实组合此前从未被完整测过」。
⑦ 三件规模难题:SRE 自己的工具也要测(测试充分度决定它多久能对生产用一次);灾难恢复工具离线测(算出等价于干净停机的检查点再校验);速度就是可用性(不稳定测试比没有测试更糟)。
⑧ 控制回路:测试变好 → MTBF 上升 → 发布变快 → 新 bug 出现 → 速度压回;落地形态就是错误预算。
⑨ 最该带走的判断:别问「测试够不够」,问「这个 bug 会在哪一格被抓到、那一格的 MTTR 是多少」。