混沌测试从入门到精通(容器与K8s)

🌀 混沌测试从入门到精通(第2部分)

上一章回顾

在第1部分,我们学了:

  • 混沌测试的起源(Netflix 的故事)
  • 四大原则(定义稳态、提出假设、控制爆炸半径、自动化)
  • 基础设施层的混沌实验(CPU、内存、网络故障注入)

现在让我们进入容器和 Kubernetes 的世界!


第四部分 容器与 Kubernetes 层混沌测试


第10章 Docker 容器故障注入

🧠 为什么要在容器层做混沌测试?

容器是现代微服务的"最小部署单元"。如果容器出问题了:

复制代码
┌───────────────────────────────────────────┐
│  Docker 容器的死法(不完全列表)              │
│                                           │
│  ☠️ 容器 OOM (内存超限)                     │
│  ☠️ 容器被 OOMKill (宿主机内存不足)           │
│  ☠️ 健康检查失败被 K8s 杀掉                  │
│  ☠️ 挂载卷丢失                              │
│  ☠️ 网络被隔离                              │
│  ☠️ 被 docker daemon 重启                   │
└───────────────────────────────────────────┘

混沌测试的目标: 确保你的应用在容器出问题时仍然可用。

💻 Demo: Docker 容器级故障注入

bash 复制代码
# █████ 场景1:杀掉某个容器 █████
# 先看看有哪些容器在跑
docker ps

# CONTAINER ID   IMAGE         STATUS         NAMES
# 1a2b3c4d5e6f   user-api      Up 2 hours     user-service
# 2b3c4d5e6f7g   order-api     Up 2 hours     order-service
# 3c4d5e6f7g8h   mysql:8.0     Up 3 hours     mysql-primary

# 杀掉订单服务容器
docker kill order-service

# 观察:系统还能正常工作吗?
# 如果订单服务有关联的 K8s Deployment, 它应该会自动重启
docker ps | grep order-service
# 如果看到新的容器已经在运行了 → ✅ 自动恢复机制正常

# █████ 场景2:暂停容器(模拟进程挂起)█████
docker pause user-service

# 看看健康检查
curl -v http://user-service/health
# 超时!容器被暂停了,进程无法响应

# 恢复
docker unpause user-service

# 再次检查
curl -v http://user-service/health
# 应该正常了

# █████ 场景3:对容器的网络进行故障注入 █████
# 使用 docker run 启动一个带网络故障的容器
docker run --rm \
  --cap-add=NET_ADMIN \
  alpine:latest \
  sh -c "tc qdisc add dev eth0 root netem delay 500ms loss 30% && \
         ping -c 10 google.com"
# 观察:ping 的延迟应该在 500ms 左右,且有 30% 的丢包

第11章 Kubernetes 混沌测试------基础设施层的核心战场

🧠 概念理解

Kubernetes 是目前最流行的容器编排平台。它自带了很多自愈能力,但你真的相信这些能力吗?
#mermaid-svg-8XJ5cifKczwI8xgd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8XJ5cifKczwI8xgd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8XJ5cifKczwI8xgd .error-icon{fill:#552222;}#mermaid-svg-8XJ5cifKczwI8xgd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8XJ5cifKczwI8xgd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8XJ5cifKczwI8xgd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8XJ5cifKczwI8xgd .marker.cross{stroke:#333333;}#mermaid-svg-8XJ5cifKczwI8xgd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8XJ5cifKczwI8xgd p{margin:0;}#mermaid-svg-8XJ5cifKczwI8xgd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8XJ5cifKczwI8xgd .cluster-label text{fill:#333;}#mermaid-svg-8XJ5cifKczwI8xgd .cluster-label span{color:#333;}#mermaid-svg-8XJ5cifKczwI8xgd .cluster-label span p{background-color:transparent;}#mermaid-svg-8XJ5cifKczwI8xgd .label text,#mermaid-svg-8XJ5cifKczwI8xgd span{fill:#333;color:#333;}#mermaid-svg-8XJ5cifKczwI8xgd .node rect,#mermaid-svg-8XJ5cifKczwI8xgd .node circle,#mermaid-svg-8XJ5cifKczwI8xgd .node ellipse,#mermaid-svg-8XJ5cifKczwI8xgd .node polygon,#mermaid-svg-8XJ5cifKczwI8xgd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8XJ5cifKczwI8xgd .rough-node .label text,#mermaid-svg-8XJ5cifKczwI8xgd .node .label text,#mermaid-svg-8XJ5cifKczwI8xgd .image-shape .label,#mermaid-svg-8XJ5cifKczwI8xgd .icon-shape .label{text-anchor:middle;}#mermaid-svg-8XJ5cifKczwI8xgd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8XJ5cifKczwI8xgd .rough-node .label,#mermaid-svg-8XJ5cifKczwI8xgd .node .label,#mermaid-svg-8XJ5cifKczwI8xgd .image-shape .label,#mermaid-svg-8XJ5cifKczwI8xgd .icon-shape .label{text-align:center;}#mermaid-svg-8XJ5cifKczwI8xgd .node.clickable{cursor:pointer;}#mermaid-svg-8XJ5cifKczwI8xgd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8XJ5cifKczwI8xgd .arrowheadPath{fill:#333333;}#mermaid-svg-8XJ5cifKczwI8xgd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8XJ5cifKczwI8xgd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8XJ5cifKczwI8xgd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8XJ5cifKczwI8xgd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8XJ5cifKczwI8xgd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8XJ5cifKczwI8xgd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8XJ5cifKczwI8xgd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8XJ5cifKczwI8xgd .cluster text{fill:#333;}#mermaid-svg-8XJ5cifKczwI8xgd .cluster span{color:#333;}#mermaid-svg-8XJ5cifKczwI8xgd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8XJ5cifKczwI8xgd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8XJ5cifKczwI8xgd rect.text{fill:none;stroke-width:0;}#mermaid-svg-8XJ5cifKczwI8xgd .icon-shape,#mermaid-svg-8XJ5cifKczwI8xgd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8XJ5cifKczwI8xgd .icon-shape p,#mermaid-svg-8XJ5cifKczwI8xgd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8XJ5cifKczwI8xgd .icon-shape .label rect,#mermaid-svg-8XJ5cifKczwI8xgd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8XJ5cifKczwI8xgd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8XJ5cifKczwI8xgd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8XJ5cifKczwI8xgd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 混沌测试验证的假设
K8s 的自愈机制
ReplicaSet

确保副本数
自动重启Pod
健康检查

Liveness/Readiness
自动扩缩容

HPA/VPA
节点调度

再调度
Pod挂了会被重建吗?
Node挂了Pod会迁移吗?
HPA能应对突发流量吗?
配置错误时服务能回滚吗?

📊 K8s 混沌测试的假设一览

故障场景 典型假设 使用的机制
Pod 被杀 K8s 会重建 Pod,服务不受影响 ReplicaSet, LivenessProbe
Node 宕机 Pod 会被调度到其他节点 NodeController, Scheduler
资源不足 HPA 会自动扩容 HorizontalPodAutoscaler
配置错误 旧版本配置仍可工作 Rollback, History
网络插件故障 所有 Pod 间通信中断 CNI 插件
DNS 故障 服务发现通过 Service IP 而非域名 kube-dns / CoreDNS

💻 Demo: 使用 kubectl 进行 K8s 混沌实验

动手前必读: 下面的命令在 minikube 或 kind 本地集群上最安全。如果你在真实集群上操作,务必 使用 --dry-run=client 先预览。

场景1:Pod 级故障注入
bash 复制代码
# █████ 实验1:删除 Pod(看 Deployment 是否重建)█████

# 1. 查看当前 Pod 状态
kubectl get pods -n production
# NAME                              READY   STATUS
# order-service-7689d8f6b7-abc12   1/1     Running
# order-service-7689d8f6b7-def34   1/1     Running  
# order-service-7689d8f6b7-ghi56   1/1     Running

# 2. 记录稳态指标(假设:请求成功率 > 99.9%)
#    打开另一个终端,持续监控
watch -n 1 "curl -s -o /dev/null -w '%{http_code}' http://order-service/metrics"

# 3. 注入故障:强制删除一个 Pod
kubectl delete pod order-service-7689d8f6b7-abc12 --force --grace-period=0

# 4. 观察自动恢复
kubectl get pods -n production -w
# order-service-7689d8f6b7-abc12   Terminating
# order-service-7689d8f6b7-xyz78   0/1     ContainerCreating
# order-service-7689d8f6b7-xyz78   1/1     Running    ← 新 Pod 起来了

# 5. 验证假设
# 查看 Pod 重建历史
kubectl rollout history deployment/order-service -n production
场景2:节点级故障注入
bash 复制代码
# █████ 实验2:模拟节点不可用 █████

# 1. 列出所有节点
kubectl get nodes
# NAME           STATUS   ROLES
# node-1         Ready    worker
# node-2         Ready    worker
# node-3         Ready    worker

# 2. 安全的方式:先 cordon(标记为不可调度)
#    这样不会驱逐已有 Pod,但不再接收新 Pod
kubectl cordon node-1

# 3. 查看 Pod 调度情况
kubectl get pods -o wide | grep node-1
# 已经运行在 node-1 上的 Pod 继续运行,但新 Pod 不会调度到 node-1

# 4. 更激进的方式:drain(驱逐所有 Pod)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

# 5. 查看 Pod 是否被重新调度到其他节点
kubectl get pods -o wide

# 6. 恢复节点
kubectl uncordon node-1
场景3:使用 Chaos Mesh 进行 K8s 混沌测试

Chaos Mesh 是专为 K8s 设计的混沌工程平台,它用 CRD(Custom Resource Definition)定义混沌实验:

yaml 复制代码
# pod-kill-experiment.yaml
# 一个完整的 Chaos Mesh 实验定义
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: order-service-pod-kill
  namespace: chaos-testing
spec:
  action: pod-kill
  mode: one  # 每次只杀一个 Pod
  duration: "60s"
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  scheduler:
    cron: "@every 10m"  # 每 10 分钟执行一次
bash 复制代码
# 应用这个混沌实验
kubectl apply -f pod-kill-experiment.yaml

# 查看实验状态
kubectl get podchaos -n chaos-testing
# NAME                      ACTION     STATE    AGE
# order-service-pod-kill    pod-kill   Running  1h

# 查看实验详情
kubectl describe podchaos order-service-pod-kill -n chaos-testing

# 停止实验
kubectl delete -f pod-kill-experiment.yaml

🎯 更复杂的 K8s 故障注入场景

yaml 复制代码
# network-chaos-experiment.yaml
# 模拟网络分区:让 order-service 无法访问 payment-service
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: network-partition
  namespace: chaos-testing
spec:
  action: partition
  mode: all
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  direction: to
  target:
    mode: all
    selector:
      namespaces:
        - production
      labelSelectors:
        app: payment-service
  duration: "180s"  # 网络分区持续 3 分钟
yaml 复制代码
# stress-chaos-experiment.yaml
# 给某个 Pod 注入 CPU 压力
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: order-service-cpu-stress
  namespace: chaos-testing
spec:
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  stressors:
    cpu:
      workers: 2
      load: 80  # 占用 80% CPU
      options: ["--cpu 2", "--timeout 60"]
  duration: "120s"

🧪 动手实践:K8s 混沌测试计划

场景: 你有一个 5 节点的 K8s 集群,运行了 10 个微服务。你需要验证:

  1. 如果突然关掉 1 个 worker 节点,系统是否正常?
  2. 如果某个服务的 Pod 数从 3 降到 1,响应时间会超过 SLO 吗?
  3. 如果 CoreDNS 挂了,服务还能通过 IP 通信吗?

用上面的模板写一个混沌实验计划...


第12章 使用 LitmusChaos 进行 K8s 混沌测试

🧠 为什么需要专门的混沌工具?

直接用 kubectl delete pod 虽然快,但不够规范

手动操作 混沌工具
不可重复 可编排、可重复
无安全护栏 内置爆炸半径控制
无结果记录 自动记录实验结果
难以团队协作 实验即代码(GitOps)
无仪表盘 可视化控制台

💻 Demo: 使用 LitmusChaos 执行混沌实验

bash 复制代码
# 1. 安装 LitmusChaos
helm repo add litmuschaos https://litmuschaos.github.io/litmus-helm/
helm install chaos litmuschaos/litmus \
  --namespace=litmus \
  --create-namespace \
  --set portal.frontend.service.type=NodePort

# 2. 验证安装
kubectl get pods -n litmus
# NAME                               READY   STATUS
# litmusportal-frontend-xxx          1/1     Running
# litmusportal-server-xxx            1/1     Running
# chaos-operator-ce-xxx              1/1     Running
# chaos-exporter-xxx                 1/1     Running

# 3. 创建一个混沌实验(实验即代码)
cat <<EOF | kubectl apply -f -
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: engine-nginx
  namespace: default
spec:
  appinfo:
    appns: "default"
    applabel: "app=nginx"
    appkind: "deployment"
  engineState: "active"
  chaosServiceAccount: litmus-admin
  experiments:
    - name: pod-delete
      spec:
        components:
          env:
            - name: TOTAL_CHAOS_DURATION
              value: "30"        # 实验持续 30 秒
            - name: CHAOS_INTERVAL
              value: "10"        # 每 10 秒删除一个 Pod
            - name: FORCE
              value: "true"      # 强制删除
            - name: TARGET_PODS
              value: "1"         # 目标 Pod 数量
            - name: PODS_AFFECTED_PERC
              value: "50"        # 影响 50% 的 Pods
EOF

# 4. 查看实验执行状态
kubectl describe chaosengine engine-nginx -n default

# 5. 查看实验结果
kubectl get chaosresult -n default
# NAME                  STATUS   EXPERIMENT   TARGET
# engine-nginx-pod-delete  Pass    pod-delete   nginx

🎯 LitmusChaos 的混沌实验中心(ChaosHub)

LitmusChaos 提供了一个预构建的实验市场(ChaosHub),包含了超过 100 个混沌实验:
#mermaid-svg-2r2ML8psiskxnpPL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2r2ML8psiskxnpPL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2r2ML8psiskxnpPL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2r2ML8psiskxnpPL .error-icon{fill:#552222;}#mermaid-svg-2r2ML8psiskxnpPL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2r2ML8psiskxnpPL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2r2ML8psiskxnpPL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2r2ML8psiskxnpPL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2r2ML8psiskxnpPL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2r2ML8psiskxnpPL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2r2ML8psiskxnpPL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2r2ML8psiskxnpPL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2r2ML8psiskxnpPL .marker.cross{stroke:#333333;}#mermaid-svg-2r2ML8psiskxnpPL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2r2ML8psiskxnpPL p{margin:0;}#mermaid-svg-2r2ML8psiskxnpPL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2r2ML8psiskxnpPL .cluster-label text{fill:#333;}#mermaid-svg-2r2ML8psiskxnpPL .cluster-label span{color:#333;}#mermaid-svg-2r2ML8psiskxnpPL .cluster-label span p{background-color:transparent;}#mermaid-svg-2r2ML8psiskxnpPL .label text,#mermaid-svg-2r2ML8psiskxnpPL span{fill:#333;color:#333;}#mermaid-svg-2r2ML8psiskxnpPL .node rect,#mermaid-svg-2r2ML8psiskxnpPL .node circle,#mermaid-svg-2r2ML8psiskxnpPL .node ellipse,#mermaid-svg-2r2ML8psiskxnpPL .node polygon,#mermaid-svg-2r2ML8psiskxnpPL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2r2ML8psiskxnpPL .rough-node .label text,#mermaid-svg-2r2ML8psiskxnpPL .node .label text,#mermaid-svg-2r2ML8psiskxnpPL .image-shape .label,#mermaid-svg-2r2ML8psiskxnpPL .icon-shape .label{text-anchor:middle;}#mermaid-svg-2r2ML8psiskxnpPL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2r2ML8psiskxnpPL .rough-node .label,#mermaid-svg-2r2ML8psiskxnpPL .node .label,#mermaid-svg-2r2ML8psiskxnpPL .image-shape .label,#mermaid-svg-2r2ML8psiskxnpPL .icon-shape .label{text-align:center;}#mermaid-svg-2r2ML8psiskxnpPL .node.clickable{cursor:pointer;}#mermaid-svg-2r2ML8psiskxnpPL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2r2ML8psiskxnpPL .arrowheadPath{fill:#333333;}#mermaid-svg-2r2ML8psiskxnpPL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2r2ML8psiskxnpPL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2r2ML8psiskxnpPL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2r2ML8psiskxnpPL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2r2ML8psiskxnpPL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2r2ML8psiskxnpPL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2r2ML8psiskxnpPL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2r2ML8psiskxnpPL .cluster text{fill:#333;}#mermaid-svg-2r2ML8psiskxnpPL .cluster span{color:#333;}#mermaid-svg-2r2ML8psiskxnpPL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2r2ML8psiskxnpPL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2r2ML8psiskxnpPL rect.text{fill:none;stroke-width:0;}#mermaid-svg-2r2ML8psiskxnpPL .icon-shape,#mermaid-svg-2r2ML8psiskxnpPL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2r2ML8psiskxnpPL .icon-shape p,#mermaid-svg-2r2ML8psiskxnpPL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2r2ML8psiskxnpPL .icon-shape .label rect,#mermaid-svg-2r2ML8psiskxnpPL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2r2ML8psiskxnpPL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2r2ML8psiskxnpPL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2r2ML8psiskxnpPL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LitmusChaos ChaosHub
K8s 实验
Pod Delete
Node Drain
Container Kill
基础设施实验
CPU Stress
Memory Stress
Disk Fill
Network Latency
AWS 实验
EC2 Stop
EBS Detach
RDS Failover


第五部分 应用层混沌测试


第13章 微服务之间的"暗战"

🧠 概念理解

在微服务架构中,服务之间的调用链可能长这样:
#mermaid-svg-Y8mo2pTjhL2EiUuC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Y8mo2pTjhL2EiUuC .error-icon{fill:#552222;}#mermaid-svg-Y8mo2pTjhL2EiUuC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Y8mo2pTjhL2EiUuC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .marker.cross{stroke:#333333;}#mermaid-svg-Y8mo2pTjhL2EiUuC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Y8mo2pTjhL2EiUuC p{margin:0;}#mermaid-svg-Y8mo2pTjhL2EiUuC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .cluster-label text{fill:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .cluster-label span{color:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .cluster-label span p{background-color:transparent;}#mermaid-svg-Y8mo2pTjhL2EiUuC .label text,#mermaid-svg-Y8mo2pTjhL2EiUuC span{fill:#333;color:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .node rect,#mermaid-svg-Y8mo2pTjhL2EiUuC .node circle,#mermaid-svg-Y8mo2pTjhL2EiUuC .node ellipse,#mermaid-svg-Y8mo2pTjhL2EiUuC .node polygon,#mermaid-svg-Y8mo2pTjhL2EiUuC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .rough-node .label text,#mermaid-svg-Y8mo2pTjhL2EiUuC .node .label text,#mermaid-svg-Y8mo2pTjhL2EiUuC .image-shape .label,#mermaid-svg-Y8mo2pTjhL2EiUuC .icon-shape .label{text-anchor:middle;}#mermaid-svg-Y8mo2pTjhL2EiUuC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .rough-node .label,#mermaid-svg-Y8mo2pTjhL2EiUuC .node .label,#mermaid-svg-Y8mo2pTjhL2EiUuC .image-shape .label,#mermaid-svg-Y8mo2pTjhL2EiUuC .icon-shape .label{text-align:center;}#mermaid-svg-Y8mo2pTjhL2EiUuC .node.clickable{cursor:pointer;}#mermaid-svg-Y8mo2pTjhL2EiUuC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .arrowheadPath{fill:#333333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Y8mo2pTjhL2EiUuC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Y8mo2pTjhL2EiUuC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Y8mo2pTjhL2EiUuC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Y8mo2pTjhL2EiUuC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .cluster text{fill:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC .cluster span{color:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Y8mo2pTjhL2EiUuC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Y8mo2pTjhL2EiUuC rect.text{fill:none;stroke-width:0;}#mermaid-svg-Y8mo2pTjhL2EiUuC .icon-shape,#mermaid-svg-Y8mo2pTjhL2EiUuC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Y8mo2pTjhL2EiUuC .icon-shape p,#mermaid-svg-Y8mo2pTjhL2EiUuC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Y8mo2pTjhL2EiUuC .icon-shape .label rect,#mermaid-svg-Y8mo2pTjhL2EiUuC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Y8mo2pTjhL2EiUuC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Y8mo2pTjhL2EiUuC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Y8mo2pTjhL2EiUuC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户
API Gateway
用户服务
订单服务
商品服务
支付服务
库存服务
第三方支付

支付宝/微信
用户数据库
订单数据库
交易数据库

如果支付服务突然变慢 2 秒会发生什么?

连锁反应:

复制代码
支付服务慢 2秒
  → 订单服务线程被占满
    → API Gateway 线程被占满
      → 所有用户请求都卡住
        → 整个系统不可用 🔥🔥🔥

这就是级联故障------应用层混沌测试要解决的核心问题。

💻 Demo: 使用 Java 实现延迟注入过滤器

这是一个实际的 Spring Boot 应用延迟注入代码:

java 复制代码
// DelayFilter.java --- 延迟注入过滤器
// 这个过滤器可以动态注入延迟到指定微服务的请求中

@Component
@Order(1)
public class ChaosDelayFilter implements Filter {
    
    // 配置:哪些路径需要延迟注入
    private static final Map<String, Integer> DELAY_CONFIG = new HashMap<>();
    
    static {
        // 格式: 路径模式 -> 延迟毫秒数
        DELAY_CONFIG.put("/api/orders/pay", 2000);    // 支付接口延迟 2 秒
        DELAY_CONFIG.put("/api/inventory/check", 500); // 库存检查延迟 500ms
    }
    
    private final AtomicBoolean enabled = new AtomicBoolean(false);
    
    @Override
    public void doFilter(ServletRequest request, 
                         ServletResponse response, 
                         FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI();
        
        // 检查是否需要注入延迟
        if (enabled.get() && DELAY_CONFIG.containsKey(path)) {
            int delayMs = DELAY_CONFIG.get(path);
            log.warn("🚨 CHAOS: 正在注入 {}ms 延迟到路径: {}", delayMs, path);
            try {
                Thread.sleep(delayMs); // 注入延迟!
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        chain.doFilter(request, response);
    }
    
    // 通过 API 动态启用/禁用混沌注入
    public void setEnabled(boolean enabled) {
        this.enabled.set(enabled);
    }
    
    public void addDelay(String path, int delayMs) {
        DELAY_CONFIG.put(path, delayMs);
    }
    
    public void clearDelays() {
        DELAY_CONFIG.clear();
        enabled.set(false);
    }
}

通过 API 动态控制混沌注入:

bash 复制代码
# 开启延迟注入
curl -X POST http://order-service/admin/chaos/enable

# 为特定接口注入 3 秒延迟
curl -X POST http://order-service/admin/chaos/delay \
  -H "Content-Type: application/json" \
  -d '{"path": "/api/orders/create", "delayMs": 3000}'

# 关闭所有混沌注入(紧急停止)
curl -X POST http://order-service/admin/chaos/disable

# 查看当前混沌配置
curl http://order-service/admin/chaos/status

🎭 生动的比喻:微服务的"朋友圈"

想象微服务是一个人,他们需要互相帮助完成任务:

用户服务 (小明)需要从订单服务 (小红)那里获取订单数据。

但是小红今天心情不好,回复特别慢。

小明就一直在等她,什么也不做。

渐渐地,来找小明帮忙的人也越来越慢...

这就是线程池耗尽。
#mermaid-svg-UOIImzusTuO1nTAh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UOIImzusTuO1nTAh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UOIImzusTuO1nTAh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UOIImzusTuO1nTAh .error-icon{fill:#552222;}#mermaid-svg-UOIImzusTuO1nTAh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UOIImzusTuO1nTAh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UOIImzusTuO1nTAh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UOIImzusTuO1nTAh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UOIImzusTuO1nTAh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UOIImzusTuO1nTAh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UOIImzusTuO1nTAh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UOIImzusTuO1nTAh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UOIImzusTuO1nTAh .marker.cross{stroke:#333333;}#mermaid-svg-UOIImzusTuO1nTAh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UOIImzusTuO1nTAh p{margin:0;}#mermaid-svg-UOIImzusTuO1nTAh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UOIImzusTuO1nTAh .cluster-label text{fill:#333;}#mermaid-svg-UOIImzusTuO1nTAh .cluster-label span{color:#333;}#mermaid-svg-UOIImzusTuO1nTAh .cluster-label span p{background-color:transparent;}#mermaid-svg-UOIImzusTuO1nTAh .label text,#mermaid-svg-UOIImzusTuO1nTAh span{fill:#333;color:#333;}#mermaid-svg-UOIImzusTuO1nTAh .node rect,#mermaid-svg-UOIImzusTuO1nTAh .node circle,#mermaid-svg-UOIImzusTuO1nTAh .node ellipse,#mermaid-svg-UOIImzusTuO1nTAh .node polygon,#mermaid-svg-UOIImzusTuO1nTAh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UOIImzusTuO1nTAh .rough-node .label text,#mermaid-svg-UOIImzusTuO1nTAh .node .label text,#mermaid-svg-UOIImzusTuO1nTAh .image-shape .label,#mermaid-svg-UOIImzusTuO1nTAh .icon-shape .label{text-anchor:middle;}#mermaid-svg-UOIImzusTuO1nTAh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UOIImzusTuO1nTAh .rough-node .label,#mermaid-svg-UOIImzusTuO1nTAh .node .label,#mermaid-svg-UOIImzusTuO1nTAh .image-shape .label,#mermaid-svg-UOIImzusTuO1nTAh .icon-shape .label{text-align:center;}#mermaid-svg-UOIImzusTuO1nTAh .node.clickable{cursor:pointer;}#mermaid-svg-UOIImzusTuO1nTAh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UOIImzusTuO1nTAh .arrowheadPath{fill:#333333;}#mermaid-svg-UOIImzusTuO1nTAh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UOIImzusTuO1nTAh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UOIImzusTuO1nTAh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UOIImzusTuO1nTAh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UOIImzusTuO1nTAh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UOIImzusTuO1nTAh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UOIImzusTuO1nTAh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UOIImzusTuO1nTAh .cluster text{fill:#333;}#mermaid-svg-UOIImzusTuO1nTAh .cluster span{color:#333;}#mermaid-svg-UOIImzusTuO1nTAh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UOIImzusTuO1nTAh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UOIImzusTuO1nTAh rect.text{fill:none;stroke-width:0;}#mermaid-svg-UOIImzusTuO1nTAh .icon-shape,#mermaid-svg-UOIImzusTuO1nTAh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UOIImzusTuO1nTAh .icon-shape p,#mermaid-svg-UOIImzusTuO1nTAh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UOIImzusTuO1nTAh .icon-shape .label rect,#mermaid-svg-UOIImzusTuO1nTAh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UOIImzusTuO1nTAh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UOIImzusTuO1nTAh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UOIImzusTuO1nTAh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 故障状态 - 一个人拖累所有人



等2秒
用户A
网关

线程池已满
用户B
用户C
支付服务

变慢
正常状态 - 每个人都很高效

100ms
50ms
30ms
用户A
网关
用户服务
订单服务
支付服务


第14章 超时和重试------「断路器」保护你的系统

🧠 概念理解

超时和重试是所有分布式系统的基础韧性模式。

但很多人写出来的代码是这样的:

java 复制代码
// ❌ 错误示例:没有超时的 HTTP 调用
RestTemplate restTemplate = new RestTemplate();

// 如果下游服务挂了,这个请求会一直等...
// 默认情况下,RestTemplate 的 readTimeout 是 infinity!
ResponseEntity<String> response = restTemplate.getForEntity(
    "http://slow-service/api/data", 
    String.class
);

正确的做法:

java 复制代码
// ✅ 正确示例:设置合理的超时和重试

@Configuration
public class ResilientRestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        // 设置连接超时和读取超时
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(1000)         // 连接超时:1秒
            .setConnectionRequestTimeout(500) // 从连接池获取连接的超时:500ms
            .setSocketTimeout(2000)           // 读取超时:2秒(最关键!)
            .build();
        
        // 重试机制(使用指数退避)
        HttpRequestRetryHandler retryHandler = (exception, executionCount, context) -> {
            if (executionCount > 3) {
                return false;  // 最多重试 3 次
            }
            if (exception instanceof InterruptedIOException) {
                return false;  // 中断不重试
            }
            if (exception instanceof UnknownHostException) {
                return false;  // DNS 解析失败不重试
            }
            return true;  // 其他情况重试
        };
        
        HttpClient httpClient = HttpClients.custom()
            .setDefaultRequestConfig(requestConfig)
            .setRetryHandler(retryHandler)
            .build();
        
        return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
    }
}

💻 Demo: 使用 Resilience4j 实现断路器

Resilience4j 是 Java 生态中最流行的韧性库(Hystrix 的继任者):

java 复制代码
// 1. 定义断路器配置
@Configuration
public class ChaosResilienceConfig {
    
    @Bean
    public CircuitBreakerConfig circuitBreakerConfig() {
        return CircuitBreakerConfig.custom()
            .failureRateThreshold(50)           // 错误率超过 50% 打开断路器
            .waitDurationInOpenState(Duration.ofSeconds(10)) // 打开后等待10秒
            .permittedNumberOfCallsInHalfOpenState(3) // 半开后允许3个试探请求
            .slidingWindowSize(10)              // 滑动窗口大小:最近10个请求
            .recordExceptions(IOException.class, TimeoutException.class)
            .build();
    }
    
    @Bean
    public TimeLimiterConfig timeLimiterConfig() {
        return TimeLimiterConfig.custom()
            .timeoutDuration(Duration.ofSeconds(2))
            .build();
    }
    
    @Bean
    public RetryConfig retryConfig() {
        return RetryConfig.custom()
            .maxAttempts(3)                     // 最多重试 3 次
            .waitDuration(Duration.ofMillis(500)) // 重试间隔 500ms
            .intervalFunction(IntervalFunction.ofExponentialBackoff())
            .retryExceptions(TimeoutException.class, IOException.class)
            .build();
    }
}

// 2. 使用断路器保护你的服务调用
@Service
public class PaymentService {
    
    private final CircuitBreaker circuitBreaker;
    private final TimeLimiter timeLimiter;
    private final Retry retry;
    
    public PaymentService(CircuitBreakerRegistry cbRegistry,
                          TimeLimiterRegistry tlRegistry,
                          RetryRegistry retryRegistry) {
        this.circuitBreaker = cbRegistry.circuitBreaker("payment-service");
        this.timeLimiter = tlRegistry.timeLimiter("payment-service");
        this.retry = retryRegistry.retry("payment-service");
    }
    
    public PaymentResult processPayment(PaymentRequest request) {
        // 装饰器模式组合:超时 + 断路器 + 重试
        Supplier<PaymentResult> decoratedSupplier = 
            Decorators.ofSupplier(() -> callExternalPaymentGateway(request))
                .withCircuitBreaker(circuitBreaker)
                .withTimeLimiter(timeLimiter)
                .withRetry(retry)
                .decorate();
        
        // 还可以添加降级逻辑
        return Try.ofSupplier(decoratedSupplier)
            .recover(throwable -> {
                log.error("支付服务调用失败,使用降级策略", throwable);
                return PaymentResult.fallback("使用备用支付通道");
            })
            .get();
    }
    
    private PaymentResult callExternalPaymentGateway(PaymentRequest request) {
        // 实际的支付请求调用...
    }
}

📊 应用层混沌测试的常见实验

实验类型 工具/方法 验证的韧性机制
服务延迟注入 ChaosBlade/自定义Filter 超时配置、断路器
服务异常返回 Mock服务/Agent注入 降级策略、错误处理
线程池耗尽 大量并发请求 隔离机制、限流
连接池耗尽 慢速响应+大批请求 连接池配置、排队机制
依赖无响应 网络分区 超时、断路器、降级

第六部分 数据层混沌测试


第15章 数据库------系统的「心脏」

🧠 概念理解

数据库是整个系统的"状态中心"。如果数据库出问题:

复制代码
应用层出问题 → 通常是暂时的,重启就好
数据层出问题 → 可能导致**数据丢失**或**数据不一致**

所以数据层混沌测试的风险最高,但收益也最大!

💻 Demo: 数据库故障注入

场景1:MySQL 连接池耗尽
bash 复制代码
# █████ 实验:模拟大量慢查询耗光连接池 █████

# 1. 查看当前数据库连接数
mysql -e "SHOW STATUS LIKE 'Threads_connected';"
# Threads_connected  42

# 2. 注入慢查询(用 SELECT SLEEP 阻塞连接)
mysql -e "
  SELECT SLEEP(30) FROM information_schema.tables WHERE table_schema = 'test';
" &

# 或者更真实的场景:模拟一个没有索引的大表查询
mysql -e "
  SELECT * FROM orders WHERE order_status = 'PENDING' 
  AND created_at < '2020-01-01';
" &

# 3. 监控连接池
mysql -e "
  SHOW STATUS LIKE 'Threads_connected';
  SHOW STATUS LIKE 'Threads_running';
  SHOW PROCESSLIST;
" 
# 会发现连接数在飙升!

# 4. 查看你的应用的数据库连接池
curl http://your-service/actuator/health
# 如果连接池满了,你的健康检查可能都会失败!
场景2:使用 ChaosBlade 注入数据库故障
bash 复制代码
# 对 MySQL 注入延迟(模拟慢查询)
./blade create mysql delay \
  --delay 3000 \
  --offset 0 \
  --mysql-type select \
  --mysql-table orders \
  --database test \
  --host 127.0.0.1 \
  --port 3306 \
  --user root \
  --password yourpass

# 恢复
./blade destroy [UID]

📊 常见的数据库混沌实验

实验场景 具体操作 验证目标
连接池耗尽 大量慢查询 连接池大小配置、排队机制
主从切换 关闭主库 主从延迟、自动切换时间
死锁 并发事务制造死锁 死锁检测超时、重试机制
磁盘空间满 填充磁盘到 100% 监控告警、只读模式
网络隔离 阻断应用→数据库网络 连接池超时、断路器
索引损坏 模拟表损坏 数据修复流程、备份恢复
字符集错误 修改表字符集 数据校验机制
数据延迟 主从复制延迟 读写分离配置、数据一致性

第七部分 消息队列混沌测试


第16章 消息中间件------异步通信的"高速公路"

🧠 概念理解

RabbitMQ、Kafka、RocketMQ ------ 这些消息中间件是现代微服务的神经系统
#mermaid-svg-qSLyoly68deCLnGt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qSLyoly68deCLnGt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qSLyoly68deCLnGt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qSLyoly68deCLnGt .error-icon{fill:#552222;}#mermaid-svg-qSLyoly68deCLnGt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qSLyoly68deCLnGt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qSLyoly68deCLnGt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qSLyoly68deCLnGt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qSLyoly68deCLnGt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qSLyoly68deCLnGt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qSLyoly68deCLnGt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qSLyoly68deCLnGt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qSLyoly68deCLnGt .marker.cross{stroke:#333333;}#mermaid-svg-qSLyoly68deCLnGt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qSLyoly68deCLnGt p{margin:0;}#mermaid-svg-qSLyoly68deCLnGt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qSLyoly68deCLnGt .cluster-label text{fill:#333;}#mermaid-svg-qSLyoly68deCLnGt .cluster-label span{color:#333;}#mermaid-svg-qSLyoly68deCLnGt .cluster-label span p{background-color:transparent;}#mermaid-svg-qSLyoly68deCLnGt .label text,#mermaid-svg-qSLyoly68deCLnGt span{fill:#333;color:#333;}#mermaid-svg-qSLyoly68deCLnGt .node rect,#mermaid-svg-qSLyoly68deCLnGt .node circle,#mermaid-svg-qSLyoly68deCLnGt .node ellipse,#mermaid-svg-qSLyoly68deCLnGt .node polygon,#mermaid-svg-qSLyoly68deCLnGt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qSLyoly68deCLnGt .rough-node .label text,#mermaid-svg-qSLyoly68deCLnGt .node .label text,#mermaid-svg-qSLyoly68deCLnGt .image-shape .label,#mermaid-svg-qSLyoly68deCLnGt .icon-shape .label{text-anchor:middle;}#mermaid-svg-qSLyoly68deCLnGt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qSLyoly68deCLnGt .rough-node .label,#mermaid-svg-qSLyoly68deCLnGt .node .label,#mermaid-svg-qSLyoly68deCLnGt .image-shape .label,#mermaid-svg-qSLyoly68deCLnGt .icon-shape .label{text-align:center;}#mermaid-svg-qSLyoly68deCLnGt .node.clickable{cursor:pointer;}#mermaid-svg-qSLyoly68deCLnGt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qSLyoly68deCLnGt .arrowheadPath{fill:#333333;}#mermaid-svg-qSLyoly68deCLnGt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qSLyoly68deCLnGt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qSLyoly68deCLnGt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qSLyoly68deCLnGt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qSLyoly68deCLnGt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qSLyoly68deCLnGt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qSLyoly68deCLnGt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qSLyoly68deCLnGt .cluster text{fill:#333;}#mermaid-svg-qSLyoly68deCLnGt .cluster span{color:#333;}#mermaid-svg-qSLyoly68deCLnGt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qSLyoly68deCLnGt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qSLyoly68deCLnGt rect.text{fill:none;stroke-width:0;}#mermaid-svg-qSLyoly68deCLnGt .icon-shape,#mermaid-svg-qSLyoly68deCLnGt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qSLyoly68deCLnGt .icon-shape p,#mermaid-svg-qSLyoly68deCLnGt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qSLyoly68deCLnGt .icon-shape .label rect,#mermaid-svg-qSLyoly68deCLnGt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qSLyoly68deCLnGt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qSLyoly68deCLnGt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qSLyoly68deCLnGt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发送订单事件
通知
通知
通知
通知
订单服务
消息队列
库存服务
物流服务
通知服务
分析服务

如果消息队列出问题会怎样?

复制代码
消息队列挂了
  → 订单创建后无法通知库存
    → 库存不会扣减
      → 超卖!🔥

💻 Demo: Kafka 故障注入

bash 复制代码
# █████ 场景1:Kafka Broker 停机 █████

# 1. 查看 Kafka 集群状态
kafka-broker-api-versions.sh --bootstrap-server localhost:9092

# 2. 停止一个 Kafka Broker(模拟宕机)
kubectl scale statefulset kafka --replicas=2 -n kafka
# 或者直接 stop Kafka 进程
kill -9 <kafka-pid>

# 3. 查看消费者状态
kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --group order-group \
  --describe
# 观察:消费者组是否重新平衡?

# 4. 验证消息是否丢失
kafka-run-class.sh kafka.tools.GetOffsetShell \
  --broker-list localhost:9092 \
  --topic orders

# █████ 场景2:Kafka 延迟/网络分区 █████

# 使用 tc 给 Kafka 注入网络延迟
tc qdisc add dev eth0 root netem delay 1000ms 200ms
# 模拟 1秒延迟,200ms 抖动

# 观察生产者和消费者的行为
# 生产者:是否会触发重试?
# 消费者:消费延迟是否增加?
# 整体:消息端到端延迟是否超过 SLO?

# 恢复
tc qdisc del dev eth0 root netem

🎯 消息队列混沌测试假设

假设 故障注入 验证点
消息至少送达一次 Broker 故障恢复 消息不丢失
消息顺序不乱 分区领导者切换 顺序消费正确
消费者重平衡快速完成 消费者宕机 重平衡时间 < 30s
死信队列正常工作 多次消费失败 死信消息正确路由
生产端有流控机制 Broker 压力增大 背压机制生效

相关推荐
步步为营DotNet1 小时前
运用.NET Aspire 和 Native AOT 打造极致性能的云原生微服务
微服务·云原生·.net
蜀道山老天师14 小时前
K8s Secret 与 ConfigMap 配置管理及动态更新实战详解
云原生·容器·kubernetes
阿里云云原生15 小时前
Node.js 监控的终极补完:一次接入,全链路打通 APM、AI 观测与运行时健康
云原生·node.js
bukeyiwanshui17 小时前
20260624 k8s pod命令
云原生·容器·kubernetes
网络研究院18 小时前
Brave浏览器放大招引入容器功能,实现多账户安全隔离
网络·安全·容器·浏览器·数据·隐私
爱好曙光20 小时前
云原生混沌工程实战:基于Litmus的故障注入与弹性测试
云原生·kubernetes·devops·混沌工程·故障注入·litmus·弹性测试
HAPPY酷20 小时前
【ROS2】VMware + Docker 运行 Gazebo 崩溃与“假死”排查实录
qt·docker·容器
探索云原生20 小时前
Kueue 如何管理 DRA 模式下的 GPU 配额
ai·云原生·kubernetes·kueue
Aray12341 天前
论服务网格在云原生微服务项目中的设计与落地实践
微服务·云原生·架构