bigywater.tech
微服务 · 16

故障注入与混沌实验:延迟、错误、断连怎么模拟,随机杀实例时的错误率

怎么模拟延迟、错误、断连和进程被杀?各种故障在 client 眼里是什么样子?随机杀实例时,重试和摘除各能挡住多少?这篇用自己写的 TCP 代理、应用层注入和真的 kill -9 做三层故障注入。

14191 ms
代理把响应切成每次 1 个字节时,一个 20 KB 请求的耗时;client 的 2 s socket 超时一次也没有触发
75.5% → 94.2%
20% 错误加 20% 慢响应下,不重试与超时 100 ms 最多试 3 次的最终成功率
12.2% → 0.0%
随机 kill -9 副本时,轮询不重试与失败换副本重试的请求失败率

TCP 层的故障

实测 · 本机执行输出
  模式                | client 看到的结果  | 延迟中位数   
  ------------------+---------------+---------
  直连(不经过代理)         | {'200': 20}   | 1 ms    
  代理透传              | {'200': 20}   | 1 ms    
  延迟:每个数据块先等 100 ms | {'200': 20}   | 208 ms  
  重置:一连上就发 RST      | {'连接被重置': 20} | 0 ms    
  黑洞:收下请求但不转发、不回复   | {'超时': 20}    | 2003 ms 
  限速:响应 100 KB/s    | {'200': 20}   | 239 ms  
  切碎:每个字节单独发一次      | {'200': 3}    | 14191 ms
  限速和切碎都没有触发 2 s 的超时:socket 超时是“两次收到数据之间最多等多久”,只要对端还在一点一点地发,总耗时可以远远超过超时(切碎时一个请求要十几秒)

延迟、重置、黑洞、限速、切碎在 client 看来各是不同的症状;限速和切碎不会触发 socket 超时。

应用层的故障

实测 · 本机执行输出
  client 策略         | 最终成功  | 延迟中位数 | p99    | 平均每个请求发了几次
  ------------------+-------+-------+--------+-----------
  不重试,超时 1 s        | 75.5% | 8 ms  | 311 ms | 1.00      
  超时 100 ms,最多试 3 次 | 94.2% | 8 ms  | 215 ms | 1.62      

错误加慢响应下,短超时加重试能把成功率和 p99 都拉好,代价是多发请求。

杀进程

实测 · 本机执行输出
  client 策略               | 请求失败率 | 成功请求延迟中位数 | p99   | 平均每个请求发了几次 | kill -9 的次数
  ------------------------+-------+-----------+-------+------------+------------
  (a) 只连固定的一个副本           | 9.8%  | 8 ms      | 10 ms | 1.00       | 8          
  (b) 轮询,不重试              | 12.2% | 8 ms      | 10 ms | 1.00       | 8          
  (c) 轮询 + 失败换副本重试        | 0.0%  | 8 ms      | 11 ms | 1.18       | 8          
  (d) (c) + 失败的副本 1 秒内不再选 | 0.0%  | 8 ms      | 11 ms | 1.01       | 8          
  每个副本从被 kill 到重新拉起约有 0.6 s 不可用,约每 1.5 秒一轮;(a) 和 (b) 失败率的期望相同(都是一个副本不可用的时间占比,约 8 次 × 0.6 s ÷ 3 ÷ 12 s ≈ 13%),区别只是 (a) 取决于被杀的恰好是不是它;重试加换副本能把用户可见的失败压到接近 0,摘除让每个请求少白发几次

重试加换副本把用户可见的失败压到 0,摘除让白发的请求更少。

分步演示

故障注入与混沌实验
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。TCP 代理是我用 asyncio 写的(没有用 Toxiproxy);重置用 SO_LINGER 0 关闭连接来发 RST;第三节的副本是 3 个独立的 Python http.server 进程,用 kill() 即 SIGKILL 杀掉、同一端口重新拉起;client 每个请求新建连接;每种配置只跑一轮,混沌的时间点由带种子的随机数决定但进程调度有抖动
    • 没有验证:真实网络上的丢包与乱序(用 netlab/dummynet 的话见网络系列);Toxiproxy、Chaos Mesh、Litmus 等工具各自的故障类型;在 Kubernetes 里杀 Pod(见 k8s 系列的滚动更新与 preStop);磁盘和 CPU 层面的故障注入;服务端状态丢失(有状态服务被杀)的后果
    京ICP备2026067502号-1京公网安备11010502063487号