06 混沌工程中服务层故障演练

image

 

这张图片详细记录了一次针对 Java 应用程序的JVM 故障注入实验。实验的核心目的是通过人为制造 JVM CPU 满载​ 和 内存溢出(OOM),来验证分布式系统在极端条件下的容错能力和监控系统的有效性。

以下是图片中各个具体操作命令的详细含义分析:

1. 环境准备与故障注入准备阶段

命令 1:ps -aux | grep DBPlus-0.0.1-SNAPSHOT.jar

  • 含义:查找目标 Java 应用程序的进程 ID(PID)。

  • 作用:在进行任何故障注入前,必须先获取服务进程的 PID。图片中查找到的目标 PID 为 16057。

命令 2:blade prepare jvm --pid 16057

  • 含义:这是 ChaosBlade 的准备命令,针对指定的 PID(16057)挂载 Java Agent。

  • 作用:JVM 故障注入(如 OOM、CPU 满载)需要深入 JVM 内部进行操作,该命令在目标进程启动字节码增强功能,为后续的故障注入打通通道。返回的 eb1096c754f14f8c 是该准备操作的唯一 UID。

2. 核心实验:模拟 JVM CPU 挂满

命令 3:blade c jvm oom --area HEAP --wild-mode true --process java --timeout 8000

  • 含义:注入一个 堆内存溢出(OutOfMemoryError)​ 的故障。

  • 参数解析:

    • blade c:create 的简写。

    • --area HEAP:指定故障发生区域为堆内存(Java 对象存储的区域)。

    • --wild-mode true:全量模式。表示不仅当前内存不足时报错,而是强制不断申请内存,即使 JVM 通过 GC 清理了空间也继续申请,直到抛出 OOM。

    • --timeout 8000:故障注入持续 8000 毫秒(8秒)后自动停止。

  • 预期现象:Java 进程会频繁触发 Full GC,随后抛出 java.lang.OutOfMemoryError: Java heap space 异常,导致服务不可用。

3. 监控与诊断命令(JVM-CPU 挂满场景)

在触发 OOM 后,图片展示了如何使用 VisualVM​ 和 网络工具​ 进行监控,这是排查问题的关键手段:

命令 4:Java VisualVM 启动命令

bash

java -Djava.rmi.server.hostname=101.43.158.84 -Dcom.sun.management.jmxremote ... -jar DBPlus-0.0.1-SNAPSHOT.jar

  • 含义:通过添加 JMX 参数重新启动服务,以便远程监控 JVM 状态。

  • 参数解析:

    • -Dcom.sun.management.jmxremote:开启 JMX 远程监控功能。

    • -Djava.rmi.server.hostname=101.43.158.84:指定 RMI 绑定的主机 IP,确保监控工具能连上。

    • -Dcom.sun.management.jmxremote.port=1099:指定 JMX 监控端口。

    • -Dcom.sun.management.jmxremote.authenticate=false:关闭认证(测试环境专用)。

    • -Dcom.sun.management.jmxremote.ssl=false:关闭 SSL 加密(测试环境专用)。

  • 作用:允许运维人员使用 jvisualvm 工具实时查看堆内存曲线、GC 频率、线程状态和 CPU 占用率。

命令 5:lsof -i | grep java

  • 含义:列出所有与 Java 进程相关的网络连接。

  • 作用:排查网络层面的问题。

    • *:33745 (LISTEN):Java 进程正在监听本地端口,通常用于 JMX 监控(对应上面的 1099 端口)或 RMI。

    • k8s-node1:rmiregistry->36.47.129.195:12176 (ESTABLISHED):显示 Java 进程与外部 IP 建立了 RMI 连接(用于接收 ChaosBlade 的故障指令)。

    • k8s-node1:60524->k8s-node1:mysql (ESTABLISHED):核心业务连接。说明 Java 应用正在正常连接 MySQL 数据库,可用于确认服务间的通信状态。

4. 进阶实验:模拟 CPU 满载

命令 6:blade create jvm cpufullload --cpu-count 6 --pid 8261

  • 含义:注入 CPU 满载​ 故障。

  • 参数解析:

    • --cpu-count 6:指定占用 6 个 CPU 核心。

    • --pid 8261:对另一个 PID 为 8261 的 Java 进程进行操作(图片上半部分是 16057,这里是 8261,说明进行了两次不同的实验)。

  • 预期现象:目标进程的 CPU 使用率会瞬间飙升至接近 600%(如果是容器环境则看宿主机限制),导致应用无法及时处理业务逻辑,响应时间变长。

总结:实验目的与可观测性闭环

这一系列操作构建了一个完整的混沌工程实验闭环:

  1. 注入故障:通过 blade 制造 OOM 和 CPU 满载。

  2. 监控验证:通过 jvisualvm 和 lsof 确认故障对 JVM 和网络的实际影响。

  3. 容错验证:(虽然图中未展示,但这是最终目的)观察在发生上述故障时,系统的熔断、降级和自动扩缩容机制是否生效,从而保证核心业务不宕机。

 

image

 

当前展示的是针对 JVM 内部运行态进行攻击的高级混沌实验,主要涉及 CPU 满载​ 和 线程异常暴增​ 两个场景。以下是详细的命令解析及对分布式系统的潜在影响分析:

一、 核心实验命令解析

1. 模拟 JVM CPU 挂满

bash

[root@k8s-node1 ~]# blade create jvm cpufullload --cpu-count 6 --pid 8261

  • 命令含义:向 PID 为 8261 的 Java 进程注入 CPU 满载故障,强制占用 6 个 CPU 核心。

  • 参数解析:

    • --cpu-count 6:指定需要占满的 CPU 核数。

    • --pid 8261:目标 Java 进程的 ID。

  • 预期现象:该进程的 CPU 使用率将瞬间飙升至接近 600%(假设机器核数充足),导致应用无法及时处理正常的业务逻辑,接口响应时间(RT)显著拉长。

2. 模拟 Running 线程异常增多

bash

[root@k8s-node1 ~]# blade create jvm threadfull --thread-count 100 --running --pid 8261

  • 命令含义:在目标进程中强制创建并运行 100 个线程。

  • 参数解析:

    • --thread-count 100:指定创建的线程总数。

    • --running:最关键参数。它表示创建的线程不是处于休眠或等待状态,而是立即进入运行状态(RUNNABLE),疯狂抢占 CPU 时间片。

    • --pid 8261:目标进程 ID。

  • 预期现象:操作系统上下文切换(Context Switching)开销剧增,CPU 忙于在不同线程间切换而实际做无用功,导致服务访问出现严重延迟。


二、 故障传导分析:从线程暴增到服务雪崩

在微服务架构中,强制注入大量 RUNNABLE​ 线程会引发致命的级联反应:

  1. 内核态资源耗尽:大量线程的创建会瞬间消耗进程的最大文件描述符限制(ulimit -n)和内存(线程栈 Thread Stack)。

  2. CPU 调度风暴:当线程数超过物理核心数数倍时,操作系统的线程调度器会陷入瘫痪。原本毫秒级的业务处理会被拉长至秒级甚至分钟级。

  3. 连接池假死:

    • 下游服务(如数据库、Redis)的响应变慢,导致当前服务的连接池(Connection Pool)中的连接长期处于占用状态无法释放。

    • 新的请求到来时发现连接池已满,开始排队或等待。

    • 随着请求堆积,Tomcat/Jetty 的工作线程池也会被占满,最终导致网关返回 503 Service Unavailable。


三、 可观测性排查闭环

结合您作为可观测性平台负责人的角色,针对此类“线程/CPU 类”故障,应指导分布式核心项目组按以下路径排查:

1. 指标层(Metrics):发现“调度风暴”

  • 观测点:在 Prometheus 中监控 process_cpu_load 和 jvm_threads_live_threads。

  • 预期现象:

    • jvm_threads_live_threads 曲线会垂直拉升,表明线程数激增。

    • process_cpu_load 接近 100%,且系统级指标 node_cpu_seconds_total (iowait 或 steal 指标) 可能升高,表明 CPU 忙于无效调度。

2. 调用链层(Tracing):定位“长尾延迟”

  • 观测点:登录 SkyWalking 或 Jaeger。

  • 预期现象:

    • 调用链拓扑图中,受影响服务的 P99 延迟会从几十毫秒变成“红色长条”。

    • 点击具体的 Span,可以看到 thread.sleep 或 runnable 状态异常,或者底层抛出 SocketTimeoutException。

3. 日志层(Logging):捕捉“资源枯竭”

  • 观测点:在 Loki/ELK 中检索该时段内的 ERROR 日志。

  • 预期现象:可能会出现 OutOfMemoryError: unable to create new native thread(无法创建新线程)或 too many open files(文件描述符耗尽)的错误,这是线程爆炸的直接证据。


四、 行动建议

  1. 验证“快速失败”机制:

    此类实验暴露了系统在极端压力下的脆弱性。建议推动研发团队在所有 HTTP 客户端和 RPC 调用中配置合理的 Timeout(连接超时、读取超时)。在模拟故障场景下,验证服务是否能快速抛出异常并进行熔断,而不是无限期等待拖垮整个线程池。

  2. 建立“线程池隔离”规范:

    建议分布式核心项目组审视核心服务的线程池配置。对于非核心接口,应考虑使用独立的线程池进行隔离,避免某个接口的异常(如死循环或外部超时)耗尽全局的工作线程。

 

posted on 2026-08-05 15:48  luzhouxiaoshuai  阅读(12)  评论(0)    收藏  举报

导航