演练不是故障表演
故障演练的目的不是“看系统会不会挂”,而是验证三件具体的事:
- 故障是否被及时观测到;
- 保护机制是否按设计生效;
- 恢复路径是否真的可执行。
如果演练结束只留下一段“服务重启后恢复”的记录,没有观测时间线、没有保护机制的状态变化、没有恢复步骤的实际耗时,这次演练的价值接近于零。
卷二讨论的限流、熔断、舱壁、重试、超时、摘流、切换,都是“平时不生效、故障时才生效”的机制。不演练,就无法确认它们是否真的接线正确。本附录给出一套可重复、可中止、可复盘的检查表。
演练前必须确认的十项
| 序号 | 项目 | 通过标准 |
|---|---|---|
| 1 | 演练目标 | 一句话说明验证哪个机制,不写“提升稳定性” |
| 2 | 影响范围 | 明确哪些用户、接口、数据会受影响 |
| 3 | 审批 | 有负责人书面同意,含时间窗 |
| 4 | 通知 | 相关团队、客服、值班已知晓 |
| 5 | 观测 | 关键指标、日志、追踪能在同一时间轴查看 |
| 6 | 注入方式 | 可重复、可停止、作用范围明确 |
| 7 | 中止条件 | 写清什么情况下立即停止,谁有权停止 |
| 8 | 回滚步骤 | 每一步有命令、有验证、有预计耗时 |
| 9 | 数据准备 | 演练数据可重置,不影响真实业务 |
| 10 | 记录人 | 有人专门记录时间线,不参与操作 |
十项里最容易缺的是第 7 和第 10。没有中止条件,演练会从“受控实验”变成“真实事故”;没有记录人,事后只能凭记忆复盘,时间线一定失真。
目标要具体
不好的目标:
- 验证系统高可用;
- 提升故障处理能力;
- 看看会不会挂。
好的目标:
- 验证库存服务超时 2 秒时,订单接口在 500ms 内返回降级结果;
- 验证摘除一台实例后,负载在 30 秒内重新均衡,错误率不超过 0.1%;
- 验证主备切换后,写请求在 60 秒内恢复,丢失数据为零。
目标越具体,通过标准越容易写,演练结束后也越容易判断“到底验没验成”。
八个可重复场景
下面八个场景都可以用进程内注入、配置开关或受控的依赖故障实现,不依赖随机网络故障。
场景 1:实例宕机
| 项目 | 内容 |
|---|---|
| 注入方式 | 停止一个应用实例,或让健康检查失败触发摘流 |
| 预期信号 | 该实例从负载均衡移除;其他实例 QPS 上升;错误率短时上升后回落 |
| 通过标准 | 30 秒内流量重新均衡;无持续 5xx;在途请求有明确结果 |
| 中止条件 | 剩余实例错误率超过 5%,或队列持续增长 |
| 观察重点 | 摘流是否等待在途请求;客户端是否重试到其他实例 |
场景 2:依赖变慢
| 项目 | 内容 |
|---|---|
| 注入方式 | 在支付或库存适配器注入固定延迟,例如 2 秒 |
| 预期信号 | 调用方超时率上升;熔断器进入打开状态;降级逻辑生效 |
| 通过标准 | 订单接口在预算内返回;熔断后不再持续打下游;恢复后自动半开 |
| 中止条件 | 线程/协程数持续增长,或连接池耗尽 |
| 观察重点 | 超时是否沿调用链传播;重试是否放大流量 |
场景 3:依赖返回错误
| 项目 | 内容 |
|---|---|
| 注入方式 | 按比例返回 500 或业务错误码,例如 30% |
| 预期信号 | 错误率上升;重试次数上升;熔断阈值被触发 |
| 通过标准 | 错误被分类;可重试错误有限次重试;不可重试错误快速失败 |
| 中止条件 | 错误扩散到非相关接口 |
| 观察重点 | 错误码是否被正确映射;幂等是否防止重复副作用 |
场景 4:缓存失效
| 项目 | 内容 |
|---|---|
| 注入方式 | 清空本地缓存,或让 Redis 不可用 |
| 预期信号 | 回源量上升;数据库 QPS 上升;延迟上升 |
| 通过标准 | 回源被限流或合并;数据库不被打穿;延迟在预算内 |
| 中止条件 | 数据库连接池耗尽或慢查询激增 |
| 观察重点 | singleflight 是否生效;热点 key 是否被保护 |
场景 5:消息积压
| 项目 | 内容 |
|---|---|
| 注入方式 | 暂停消费者,或降低消费速率 |
| 预期信号 | 积压量上升;消费延迟上升;告警触发 |
| 通过标准 | 积压可观测;扩容或提速后能恢复;重复消费不产生重复副作用 |
| 中止条件 | 积压超过预设上限,或磁盘将满 |
| 观察重点 | 顺序是否保持;死信是否有归处;对账是否能补齐 |
场景 6:数据库主备切换
| 项目 | 内容 |
|---|---|
| 注入方式 | 在受控环境中停止主库,触发切换 |
| 预期信号 | 写请求短时失败;连接重建;读请求可能读到旧数据 |
| 通过标准 | 切换在预定时间内完成;数据丢失为零;应用自动重连 |
| 中止条件 | 切换后数据不一致,或无法确认新主 |
| 观察重点 | 应用是否有重连退避;读写分离的延迟兜底是否生效 |
场景 7:限流触发
| 项目 | 内容 |
|---|---|
| 注入方式 | 把限流阈值调低,或把流量提高到阈值以上 |
| 预期信号 | 拒绝率上升;通过量稳定在上限;延迟不再随流量上升 |
| 通过标准 | 拒绝返回明确错误码;核心接口优先;限流解除后自动恢复 |
| 中止条件 | 误伤核心链路,或拒绝比例超过业务可接受范围 |
| 观察重点 | 限流维度是否合理;客户端是否尊重 Retry-After |
场景 8:发布与回滚
| 项目 | 内容 |
|---|---|
| 注入方式 | 发布一个带已知缺陷的版本,再执行回滚 |
| 预期信号 | 金丝雀指标异常;发布暂停;回滚后指标恢复 |
| 通过标准 | 异常在影响扩大前被发现;回滚在预定时间内完成;数据兼容 |
| 中止条件 | 回滚后错误率不降,或数据出现不可逆变化 |
| 观察重点 | 兼容窗口是否足够;开关能否独立关闭新逻辑 |
观测信号对照
演练时最怕“感觉有问题但拿不出证据”。下面是最小信号集,演练前确认每一项都能查到。
| 层 | 信号 | 在哪看 |
|---|---|---|
| 业务 | 下单成功率、支付成功率 | 业务仪表盘 |
| 接口 | QPS、P99、错误率、超时率、拒绝率 | 应用指标 |
| 依赖 | 下游延迟、错误率、连接池占用 | 依赖指标 |
| 保护 | 熔断状态、限流拒绝数、重试次数 | 治理指标 |
| 资源 | CPU、内存、GC、协程数 | 主机/运行时指标 |
| 链路 | 单笔订单的 span 时间线 | 追踪系统 |
| 日志 | 错误码、状态迁移、降级原因 | 结构化日志 |
演练前做一次“空跑”:不注入故障,只确认这些信号在同一时间窗口内都能看到。空跑能发现大部分采集配置问题,比演练中临时找指标高效得多。
中止与回滚
中止不是失败,是演练的一部分。以下情况应立即停止:
- 影响范围超出审批范围;
- 错误率或延迟超过预设上限;
- 保护机制未生效,且流量持续打向下游;
- 数据出现不一致或不可逆变化;
- 值班人员无法判断当前状态。
回滚步骤要写成可执行的命令,而不是“恢复配置”。例如:
# 1. 停止注入
kill -TERM "$(cat .local/drill/inject.pid)"
# 2. 恢复限流阈值
curl -X POST localhost:8080/admin/ratelimit -d '{"rate":200}'
# 3. 确认指标恢复
curl -s localhost:8080/metrics | grep 'order_http_errors_total'
# 4. 恢复流量
curl -X POST localhost:8080/admin/traffic -d '{"weight":100}'
每一步都要有验证,不能一口气执行完再检查。回滚本身也可能失败,因此要准备“回滚的回滚”——最坏情况下如何回到演练前状态。
执行记录表
演练过程中由记录人填写,不做事后补记。
| 时间 | 事件 | 操作人 | 观测到的信号 | 结果 |
|---|---|---|---|---|
| 14:00:00 | 开始注入 | 张三 | 延迟开始上升 | 符合预期 |
| 14:00:30 | 熔断打开 | 系统 | 下游错误率 0 | 保护生效 |
| 14:01:10 | 开始回滚 | 李四 | 延迟回落 | 符合预期 |
| 14:02:00 | 指标恢复 | 系统 | P99 回到基线 | 演练结束 |
时间要精确到秒,信号要写具体数值,不写“正常/异常”。事后复盘时,这张表是唯一可靠的事实来源。
演练后复盘
复盘回答四个问题,不追责个人:
- 目标验证了吗?通过标准是否达成?
- 观测是否及时?从故障发生到被发现用了多久?
- 保护机制是否按设计生效?没有生效的原因是什么?
- 恢复步骤是否可执行?实际耗时与预计差多少?
输出至少包含:
- 一条时间线;
- 一组前后指标对比;
- 一份改进项清单,每项有负责人和截止时间;
- 对检查表本身的修订建议。
改进项要能验证。“加强监控”不是改进项,“为支付超时增加 P99 告警,阈值 800ms,持续 2 分钟”才是。
演练频率与范围
| 级别 | 频率 | 范围 | 例子 |
|---|---|---|---|
| 单机制验证 | 每次相关改动后 | 一个接口、一个实例 | 改熔断阈值后验证半开恢复 |
| 单场景演练 | 每月或每季度 | 一个服务、一个依赖 | 缓存不可用、消息积压 |
| 链路演练 | 每半年 | 一条核心链路 | 下单链路的依赖超时与切换 |
| 全站演练 | 每年,谨慎 | 多服务、基础设施 | 区域级切换,通常由专门平台执行 |
频率不是越高越好。每次演练都有成本和风险,关键是“改动之后有没有验证”。改了限流逻辑却不演练,等于把新逻辑直接交给生产验证。
演练范围应该从单机制开始,逐步扩大。跳过单机制直接做全链路,问题会叠加在一起,既难定位也难中止。
谁应该参加
| 角色 | 职责 | 不做什么 |
|---|---|---|
| 演练负责人 | 决定开始、中止和结束 | 不亲自操作注入 |
| 操作人 | 执行注入与回滚 | 不判断是否继续 |
| 记录人 | 记录时间线与信号 | 不参与操作 |
| 观察人 | 看指标、日志、追踪 | 不改配置 |
| 业务代表 | 判断用户影响是否可接受 | 不指挥技术操作 |
| 值班 | 接住演练外的真实告警 | 不被拉进演练操作 |
负责人和操作人分开,是为了避免“操作顺利就继续加码”的惯性。记录人独立,是为了保证时间线不被事后解释覆盖。
七条禁止事项
第一,不在生产环境做未经审批的演练。
第二,不注入无法停止的故障。任何注入都必须有对应的停止手段。
第三,不在没有观测的情况下演练。看不到信号,等于制造事故。
第四,不在流量高峰或大促前演练。
第五,不把演练脚本写成随机破坏。随机性留给专门的混沌平台,卷二强调可重复。
第六,不让同一个人既操作又记录。记录会被操作节奏带偏。
第七,不把演练结果当成“系统没问题”的证明。一次通过只说明这次场景、这次配置、这个时间点通过。
一页清单
打印或复制到演练工单里:
[ ] 目标一句话,含验证的机制
[ ] 影响范围与审批
[ ] 相关团队已通知
[ ] 关键信号空跑可见
[ ] 注入方式可停止
[ ] 中止条件与有权停止的人
[ ] 回滚步骤逐步可执行
[ ] 数据可重置
[ ] 记录人就位
[ ] 时间线开始记录
[ ] 演练后复盘会议已排期
[ ] 改进项有负责人和截止时间
常见错误与边界
第一,只演练“服务挂掉”。真实故障更多是变慢、部分失败和依赖超时;这些场景不会让健康检查失败,却会拖垮调用方。
第二,没有基线。演练前不记录正常指标,事后无法判断“恢复了没有”。
第三,把保护机制当成必然生效。限流、熔断、舱壁都需要配置正确、依赖可用、指标可观测,任何一环缺失都会失效。
第四,演练范围过大。一次只验证一个机制,多个故障叠加会让归因变得不可能。
第五,忽略客户端行为。服务端降级了,客户端是否重试、是否尊重错误码,决定最终用户体验。
第六,复盘没有改进项,或者改进项没有截止时间。这样的复盘只是写了一份文档。
小结
故障演练验证的是观测、保护和恢复三件事,不是“系统会不会挂”。演练前确认十项准备,选择可重复的注入方式,写清中止条件和回滚步骤;演练中由专人记录时间线;演练后输出时间线、指标对比和改进项。
八个场景覆盖实例宕机、依赖变慢、依赖报错、缓存失效、消息积压、主备切换、限流触发和发布回滚。它们都不依赖随机网络故障,适合在受控环境中反复执行。卷四会把其中一部分平台化、自动化,卷二先确保每一次演练都是可控、可观测、可复盘的。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。