系统在预发环境跑得好好的,到生产上遇到一个依赖超时就全线变慢。这类事发生几次后会让人意识到:正确性和韧性是两件事。前者说没有故障时行为对不对,后者说有故障时行为还能不能接受,而后者几乎不可能靠读代码确认。
实验的前提:先有假设
混沌工程的核心动作不是注入故障,而是验证假设,注入只是手段。
假设需要参照物,即稳态指标:一组代表”系统当前正常”的可观测指标,如请求成功率、P99 延迟、核心接口 QPS。它把”系统还好吗”变成可判定的问题,没有它,注入后看到什么都是模糊的。
一条完整的假设长这样:如果某个下游依赖的响应时间增加到 500ms,那么下单接口的成功率应在 30 秒内保持在 99.9% 以上,因为该依赖有熔断和本地缓存兜底。这句话包含了故障类型、影响范围、指标、阈值,以及机制解释。
对照之下,”随机杀一个 Pod 看看会怎样”不算实验:它没有预期,结果只能是”好像没事”或”好像有事”,无法转为改进项。
假设被证伪也是有价值的结论——它证明了一个被认为存在的兜底实际没生效,报告里最有价值的部分往往就是被证伪的假设。
四个步骤
| 步骤 | 做什么 | 产出 | 常见遗漏 |
|---|---|---|---|
| 假设 | 定义稳态指标与预期影响 | 一条可判定的陈述 | 阈值拍脑袋,事后无法判定 |
| 注入 | 选定范围、类型、时长 | 一次可控的故障 | 没有明确的撤销路径 |
| 观察 | 采集指标、对比控制组 | 偏离与否的证据 | 只看总量不看分位 |
| 复盘 | 判定假设成立与否 | 改进项与回归实验 | 发现问题但没有跟进 |
观察要有控制组:指标波动 3% 可能是注入造成,也可能是流量自然波动,有了对照组才能分开。复盘的输出必须包含回归实验——修复之后重跑同样的实验确认假设成立,没有回归,修复就只是”改了点东西”。
最小爆炸半径
爆炸半径是实验设计里最需要克制的部分,可以从几个维度收紧:
- 实例数:先注入单个实例,而不是整批。
- 依赖:先选非关键依赖,而不是核心链路上的数据库。
- 流量:先用内部账号或 1% 灰度流量,而不是全量用户。
- 时长:先注入数十秒到数分钟,而不是持续半小时。
- 地域:先在单可用区,而不是跨区。
时间窗口同样属于爆炸半径:非高峰、有值守人、有变更窗口。值守人的意义不只是”看着”,还包括实验意外扩大时能立刻决策是否中止。
常见的注入类型
| 注入类型 | 典型实现 | 验证什么 |
|---|---|---|
| 进程终止 | 杀掉容器或进程 | 重启、健康检查、流量摘除是否及时 |
| 网络延迟 | 流量控制工具加固定延迟 | 超时与重试设置是否合理 |
| 丢包与分区 | 丢弃特定目标报文 | 容错路径与数据一致性 |
| CPU / 内存压力 | 压满资源 | 容量余量与限流是否生效 |
| 依赖不可用 | 让下游返回错误或超时 | 熔断、降级、缓存兜底 |
| 时间漂移 | 调整系统时钟 | 证书校验、定时任务、缓存过期 |
| 磁盘写满 / IO 变慢 | 占满空间或限速 | 日志写入失败时是否影响主流程 |
# 给 eth0 增加 100ms 延迟、±20ms 抖动(参数以实际环境为准)
tc qdisc add dev eth0 root netem delay 100ms 20ms
# 撤销,必须与注入同等熟练
tc qdisc del dev eth0 root
延迟注入最能暴露问题。很多服务的超时设置是历史遗留:客户端超时 30 秒而服务端处理只有 20 秒,或者重试三次每次都等满超时——没有延迟的测试环境里,这些配置永远不会表现出问题。
时间漂移是容易被忽略的类型。系统时钟被调整后,依赖当前时间的逻辑会一起出错:证书校验、缓存过期、定时任务触发、基于时间的幂等窗口,它模拟的是 NTP 同步异常这类真实故障。
预发还是生产
预发环境能验证相当一部分内容,尤其是”兜底逻辑是否存在”:熔断配了没有、降级分支对不对、超时值是否合理。这些收益最大,也完全可以在预发完成。
预发验证不了的是真实环境的差异:
- 流量规模:生产并发量决定排队行为,预发压不出同样的队列深度。
- 依赖拓扑:生产调用链更长,中间多几层代理和网关,超时叠加方式不同。
- 配置:生产的连接池、线程池、限流阈值往往与预发不同,差异有时相当大。
- 数据分布:缓存命中率、慢查询比例、数据倾斜都不同。
- 监控告警覆盖:这一点常被忽略——预发上告警没人看,生产上告警是否真的触发过从未验证。
可行的路径是两段式:预发做第一轮,确认兜底逻辑和观测手段到位;生产做最终验证,从只读、低风险、小流量的实验开始。跳过预发直接上生产,多半会在实验配置上浪费时间;只做预发不做生产,则永远不知道真实拓扑下的表现。
安全网
安全网不是可选项,它是实验能持续做下去的前提。
自动停止条件。稳态指标超出阈值时自动终止实验并撤销注入。这条规则要在实验开始前配置好,且它自身要可靠——不能依赖”人看到告警后手动停止”,人的反应速度赶不上故障扩散速度。
一键中止。人工可随时停止,且中止路径不能依赖注入系统本身——注入系统自己挂掉时也必须能恢复,这是硬要求。
注入的可恢复性。每次注入都要有确定的撤销动作并事先演练过。更稳妥的做法是让注入自带超时:注入 5 分钟后自动撤销,即使注入系统崩溃故障也不会持久。
影响隔离。只有打了标记的范围允许被注入;用户侧实验先从内部账号开始,涉及数据的实验优先只读场景。
演练与通知。实验前通知相关方,明确值守人和回滚方案。实验影响的往往不只是技术团队——客服要知道可能有咨询量,业务方要知道指标可能短暂波动。
常见的失败方式
没有稳态指标就动手是最常见的一种:注入完成,看了一圈监控觉得”没什么变化”,实验结束。实际可能有变化,只是没人定义过什么算变化。
注入之后无人观察:实验自动跑完,报告生成了没人打开。这比不做更糟,它制造了”我们在做混沌工程”的错觉。
只做一次。系统韧性会随变更退化:新增一个没有超时的调用、升级依赖库、改连接池配置,一次实验的结论过几个月就失效。常态化的形式可以是定期演练日,也可以是接入流水线在关键变更前后自动跑。
没有对照组,导致结论不可信。
实验设计过大,一上来打全站,出问题后无法定位环节,反而增加对混沌工程的抵触。
只验证”系统没崩”。有些降级实现让系统保持可用却给出错误结果——订单成功但金额算错,这类问题比崩溃更严重,只看可用性的实验发现不了。
发现了问题但没有跟进。实验的价值在修复和回归,停在”记录一下”的阶段就只是形式。
判断混沌工程是否真在起作用,有个简单标准:最近一次实验有没有产生一个具体的修复项。没有的话,多半是实验设计得太温和,或者假设定义得不够具体。
Comments (0)
No comments yet — be the first.
Please Sign in to join the discussion.