2. 汽车电子系统基础

做混沌工程这几年,我最大的体会是:故障注入不是乱搞。你想想看,如果连故障类型都分不清楚,就往生产环境里扔炸弹,那跟闭着眼睛拆弹有什么区别?

这一章,咱们把故障模型和注入层次彻底聊透。说白了,就是搞清楚两件事:能注入什么故障,以及在哪个层面注入。

故障模型分类:崩溃、延迟、错误、篡改

我习惯把故障模型分成四大类。这四类几乎覆盖了线上所有你能遇到的异常场景。

故障类型 核心表现 典型场景
崩溃(Crash) 进程/服务/节点突然停止响应 OOM、段错误、硬件宕机
延迟(Latency) 响应时间显著增加,但未完全中断 网络抖动、磁盘 I/O 瓶颈、GC 停顿
错误(Error) 返回异常状态码或错误数据 HTTP 500、数据库连接超时、认证失败
篡改(Tamper) 数据或配置被非预期修改 缓存污染、配置中心误操作、中间人攻击

我的经验之谈: 很多团队只关注「崩溃」和「延迟」,觉得服务没挂、响应没慢就万事大吉。但我在项目中遇到过好几次「错误」和「篡改」引发的连锁故障——比如某个配置项被意外篡改,导致整个集群的负载均衡策略失效,最终引发雪崩。所以,四类故障都要覆盖,一个都不能少。

1. 崩溃(Crash)

崩溃是最直观的故障。进程没了,端口不通了,心跳断了。嗯,这没什么好说的。

但要注意:崩溃不一定是瞬间的。我曾经遇到过 Java 进程 OOM 后,JVM 还在缓慢地做最后的 GC 尝试,端口虽然还开着,但已经无法处理任何请求。这种「半死不活」的状态,比直接崩溃更难排查。

注入建议: 用 kill -9 模拟硬崩溃,用 kill -15 模拟优雅关闭。两者表现完全不同,你的系统应该都能应对。

2. 延迟(Latency)

延迟是混沌工程里最「阴险」的故障。为什么?因为系统不会直接挂掉,但用户体验会急剧下降,而且很难快速定位根因。

我个人习惯把延迟分为两类:

  • 固定延迟: 每次请求都增加固定的等待时间,比如 500ms。适合测试超时机制。
  • 抖动延迟: 延迟时间随机变化,比如 100ms~2s 之间波动。更贴近真实网络环境。

我记得有一次线上事故,就是某个中间件偶尔出现 200ms 的抖动延迟。监控上看平均延迟只涨了 30ms,但 P99 延迟直接飙到了 3 秒。如果只盯着平均值,你永远发现不了问题。

3. 错误(Error)

错误故障的核心是:服务还在,但返回了错误。比如 HTTP 503、数据库查询超时、RPC 调用返回异常码。

这里有个坑——错误码的语义。我曾经在项目中遇到一个情况:我们注入了一个「数据库连接池耗尽」的错误,但客户端代码把所有的异常都当成「可重试」来处理,结果导致重试风暴,直接把数据库打挂了。所以,注入错误时,一定要搞清楚你的客户端是怎么处理这些错误码的。

避坑指南: 不要只注入 500 错误。试试 429(限流)、503(服务不可用)、504(网关超时)。不同错误码的客户端行为差异很大。

4. 篡改(Tamper)

篡改是最容易被忽视的故障类型。它不直接破坏服务,而是悄悄地改变数据或配置,让系统在「看起来正常」的状态下做出错误决策。

常见的篡改场景:

  • 缓存中的 key 被篡改,导致返回错误数据
  • 配置中心的某个开关被意外翻转
  • 数据库中的关键字段被修改
  • 请求头中的认证信息被篡改

嗯,篡改故障的排查难度是最高的。因为监控通常只检查「服务是否存活」「响应是否正常」,但不会检查「数据是否正确」。我建议在注入篡改故障时,一定要配合数据校验的监控,否则你根本不知道故障已经发生了。

故障注入的层次:基础设施、平台、应用

聊完故障模型,咱们再来看注入层次。我习惯把系统分成三层:基础设施层、平台层、应用层。每一层能注入的故障类型和影响范围都不一样。

故障注入层次结构 应用层(Application) 业务代码、微服务、API 网关、定时任务 注入方式:方法延迟、返回错误、篡改参数、抛出异常 平台层(Platform) 中间件(MySQL、Redis、Kafka)、容器编排、服务网格 注入方式:连接池耗尽、主从切换、Topic 分区故障 基础设施层(Infrastructure) 服务器、网络、磁盘、DNS、负载均衡器 注入方式:节点宕机、网络丢包、磁盘 IO 限速、DNS 劫持 故障影响范围:基础设施层 > 平台层 > 应用层;但排查难度正好相反

1. 基础设施层

基础设施层是最底层,也是最「暴力」的注入层次。说白了,就是直接对物理或虚拟资源动手。

常见的注入手段:

  • 节点宕机: 直接 shutdown 一台服务器
  • 网络故障: 丢包、延迟、分区(network partition)
  • 磁盘故障: IO 限速、磁盘写满、磁盘只读
  • DNS 故障: 域名解析失败或返回错误 IP

我在项目中遇到过最经典的基础设施故障是「磁盘写满」。当时监控显示磁盘使用率 85%,看起来还好。但实际上某个日志文件被删除了但进程还持有文件句柄,导致磁盘空间没有真正释放。嗯,这种故障,光靠基础设施层的监控是不够的,还得配合平台层的文件系统检查。

注入建议: 基础设施层的故障影响范围最大,建议先从「非核心节点」开始注入。别一上来就把主库所在的机器给 shutdown 了。

2. 平台层

平台层指的是中间件和容器编排层。这一层的故障,往往比基础设施层更「隐蔽」,但影响面同样不小。

常见的注入场景:

  • 数据库: 连接池耗尽、慢查询、主从切换延迟
  • 缓存: Redis 宕机、缓存穿透、缓存雪崩
  • 消息队列: Topic 分区不可用、消费积压、消息丢失
  • 服务网格: Sidecar 注入延迟、Envoy 配置错误

我个人觉得,平台层的故障注入是最「划算」的。为什么?因为这一层的故障既能暴露应用代码的缺陷,又不会像基础设施层那样容易引发大规模不可用。你想想看,如果 Redis 挂了,你的应用有没有降级方案?如果数据库主库切换了,你的连接池会不会瞬间爆掉?这些都是平台层故障能帮你验证的。

3. 应用层

应用层离业务代码最近。这一层的故障注入,说白了就是在代码层面制造异常。

常见的注入方式:

  • 方法延迟: 在某个关键方法中增加 sleep
  • 返回错误: 强制让某个接口返回 500 或自定义错误码
  • 参数篡改: 修改请求中的关键参数
  • 抛出异常: 在特定条件下抛出运行时异常

我记得有一次,我们在一个支付服务中注入了「订单状态篡改」的故障。结果发现,下游的对账系统完全没有做数据校验,直接把篡改后的状态写入了财务数据库。嗯,这个故障要是发生在生产环境,后果不堪设想。

避坑指南: 应用层的故障注入要特别注意「副作用」。比如你篡改了一个订单金额,这个数据可能会流入多个下游系统。注入前一定要想清楚:这个故障会不会污染真实数据? 如果会,记得注入完成后做数据清理。

故障模型 × 注入层次:组合矩阵

把故障模型和注入层次组合起来,就形成了一个 4×3 的矩阵。我建议你在设计混沌实验时,至少覆盖矩阵中的 70% 的组合。

故障模型 \ 注入层次 基础设施层 平台层 应用层
崩溃 节点宕机 Redis 主节点宕机 进程 OOM
延迟 网络延迟 100ms 数据库连接池等待 方法内 sleep(500)
错误 DNS 解析失败 MySQL 连接超时 接口返回 503
篡改 iptables 规则篡改 配置中心 Key 被改 请求参数被篡改

我的建议: 不要一次性覆盖所有组合。先从「应用层 × 错误」开始,因为这一组合的影响范围最小,排查也最容易。等团队积累了经验,再逐步扩展到平台层和基础设施层。