7AA7 Blog

混沌工程入门:用故障注入验证系统韧性

周牧野 UID:105
2026-10-17 13:41
10 0 0

系统在预发环境跑得好好的,到生产上遇到一个依赖超时就全线变慢。这类事发生几次后会让人意识到:正确性和韧性是两件事。前者说没有故障时行为对不对,后者说有故障时行为还能不能接受,而后者几乎不可能靠读代码确认。

实验的前提:先有假设

混沌工程的核心动作不是注入故障,而是验证假设,注入只是手段。

假设需要参照物,即稳态指标:一组代表”系统当前正常”的可观测指标,如请求成功率、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 分钟后自动撤销,即使注入系统崩溃故障也不会持久。

影响隔离。只有打了标记的范围允许被注入;用户侧实验先从内部账号开始,涉及数据的实验优先只读场景。

演练与通知。实验前通知相关方,明确值守人和回滚方案。实验影响的往往不只是技术团队——客服要知道可能有咨询量,业务方要知道指标可能短暂波动。

常见的失败方式

没有稳态指标就动手是最常见的一种:注入完成,看了一圈监控觉得”没什么变化”,实验结束。实际可能有变化,只是没人定义过什么算变化。

注入之后无人观察:实验自动跑完,报告生成了没人打开。这比不做更糟,它制造了”我们在做混沌工程”的错觉。

只做一次。系统韧性会随变更退化:新增一个没有超时的调用、升级依赖库、改连接池配置,一次实验的结论过几个月就失效。常态化的形式可以是定期演练日,也可以是接入流水线在关键变更前后自动跑。

没有对照组,导致结论不可信。

实验设计过大,一上来打全站,出问题后无法定位环节,反而增加对混沌工程的抵触。

只验证”系统没崩”。有些降级实现让系统保持可用却给出错误结果——订单成功但金额算错,这类问题比崩溃更严重,只看可用性的实验发现不了。

发现了问题但没有跟进。实验的价值在修复和回归,停在”记录一下”的阶段就只是形式。

判断混沌工程是否真在起作用,有个简单标准:最近一次实验有没有产生一个具体的修复项。没有的话,多半是实验设计得太温和,或者假设定义得不够具体。

193Views
10Likes
0Comments
0Participants
7 minread time

Comments (0)

No comments yet — be the first.