微服务拆分精讲 / 第 17 章:故障排查
第 17 章:故障排查
分布式系统的故障排查是另一维度的挑战。一个请求经过 10 个服务,问题可能出在任何一个环节。
17.1 微服务常见故障类型
17.1.1 故障分类
┌──────────────────────────────────────────────────────────────┐
│ 微服务故障分类 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 网络故障 │ │ 服务故障 │ │ 数据故障 │ │
│ ├────────────────┤ ├────────────────┤ ├────────────────┤ │
│ │ • 网络分区 │ │ • 服务崩溃 │ │ • 数据不一致 │ │
│ │ • DNS 解析失败 │ │ • OOM │ │ • 数据丢失 │ │
│ │ • 连接超时 │ │ • 死锁 │ │ • 数据损坏 │ │
│ │ • 带宽不足 │ │ • 线程池耗尽 │ │ • 缓存穿透 │ │
│ │ • 负载不均 │ │ • 内存泄漏 │ │ • 缓存雪崩 │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 配置故障 │ │ 安全故障 │ │ 依赖故障 │ │
│ ├────────────────┤ ├────────────────┤ ├────────────────┤ │
│ │ • 配置错误 │ │ • 认证失败 │ │ • 上游服务不可用│ │
│ │ • 环境变量缺失 │ │ • 证书过期 │ │ • 下游服务超时 │ │
│ │ • 版本不兼容 │ │ • Token 失效 │ │ • 第三方 API 故障│ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
└──────────────────────────────────────────────────────────────┘
17.1.2 故障频率排行
| 排名 |
故障类型 |
频率 |
影响范围 |
| 1 |
网络超时 |
极高 |
局部-全局 |
| 2 |
服务不可用 |
高 |
局部 |
| 3 |
内存泄漏/OOM |
中 |
单服务 |
| 4 |
数据库连接池耗尽 |
中 |
单服务-关联服务 |
| 5 |
配置错误 |
中 |
单服务 |
| 6 |
线程池/连接池死锁 |
低 |
单服务 |
| 7 |
数据不一致 |
低 |
业务层 |
17.2 分布式调试方法论
17.2.1 调试流程(RED 方法)
RED 调试法:
R - Reproduce (复现)
──────────────────
• 确认问题现象
• 收集错误日志和时间范围
• 确定影响范围
E - Explore (探索)
──────────────────
• 查看链路追踪(TraceID)
• 查看每个服务的日志
• 查看监控指标(QPS、延迟、错误率)
D - Diagnose (诊断)
──────────────────
• 定位问题根因
• 验证假设
• 制定修复方案
17.2.2 使用 TraceID 串联调试
场景:用户反馈下单失败
Step 1: 从用户请求中获取 TraceID
──────────────────────────────────
请求 Header: X-Request-ID: req-abc-123-def
Step 2: 在链路追踪系统中搜索
──────────────────────────────────
Jaeger/Tempo 搜索: traceID = req-abc-123-def
Trace 结果:
│
├── API Gateway (5ms) ✅
│ └── Order Service (150ms) ❌ 超时
│ ├── User Service (10ms) ✅
│ ├── Product Service (15ms) ✅
│ ├── Inventory Service (50ms) ✅
│ └── Payment Service (85ms) ❌ timeout!
│ └── Bank API: 连接超时
Step 3: 定位根因
─────────────────
Payment Service → Bank API 的调用超时
原因:银行 API 网络抖动
Step 4: 查看相关日志
─────────────────────
Loki 查询: {service="payment-service"} | json | traceId="req-abc-123-def"
日志结果:
[ERROR] Failed to call Bank API: Connection timeout after 5000ms
[WARN] Circuit breaker OPEN for Bank API
17.2.3 常用调试命令
# 1. 查看 Pod 状态
kubectl get pods -n production -l app=order-service
kubectl describe pod order-service-xxx -n production
# 2. 查看 Pod 日志
kubectl logs order-service-xxx -n production --tail=100
kubectl logs order-service-xxx -n production --previous # 查看上一个容器的日志
# 3. 进入容器调试
kubectl exec -it order-service-xxx -n production -- /bin/sh
# 4. 查看资源使用
kubectl top pods -n production -l app=order-service
# 5. 查看服务端点
kubectl get endpoints order-service -n production
# 6. DNS 解析测试
kubectl run debug --image=busybox -it --rm -- nslookup order-service.production.svc.cluster.local
# 7. 网络连通测试
kubectl run debug --image=curlimages/curl -it --rm -- curl -v http://order-service:8080/actuator/health
17.3 性能排查
17.3.1 性能问题定位流程
性能问题定位:
┌──────────────────────────────────────────────────────┐
│ 1. 确认问题现象 │
│ ├── P99 延迟从 100ms 升到 2s │
│ ├── 仅影响订单服务 │
│ └── 上午 10 点开始 │
└───────────────────────┬──────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 2. 查看基础设施指标 │
│ ├── CPU: 订单服务 Pod CPU 90%+ ❌ │
│ ├── Memory: 正常 60% │
│ ├── Network: 正常 │
│ └── Disk: 正常 │
└───────────────────────┬──────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 3. 查看应用指标 │
│ ├── JVM Heap: 正常 │
│ ├── GC: Full GC 频率增加 │
│ ├── Thread Count: 线程数 500+ (正常 200) ❌ │
│ ├── DB Connection: 连接池 50/50 (满) ❌ │
│ └── HTTP Connections: 大量 CLOSE_WAIT ❌ │
└───────────────────────┬──────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 4. 查看链路追踪 │
│ ├── 大量请求卡在 DB 查询 (平均 800ms) │
│ ├── DB 查询:SELECT * FROM orders WHERE ... (慢查询)│
│ └── 缺少索引 │
└───────────────────────┬──────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 5. 根因分析 │
│ 慢 SQL → 连接池等待 → 线程堆积 → CPU 升高 → 延迟飙升│
└───────────────────────┬──────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 6. 修复 │
│ ├── 添加数据库索引 │
│ ├── 优化 SQL 查询 │
│ └── 增加连接池大小 │
└──────────────────────────────────────────────────────┘
17.3.2 性能排查工具箱
| 工具 |
用途 |
使用场景 |
| Prometheus + Grafana |
指标监控 |
宏观性能趋势 |
| Jaeger/Tempo |
链路追踪 |
请求链路瓶颈 |
| Arthas |
Java 在线诊断 |
线程/方法级分析 |
| async-profiler |
Java 性能剖析 |
CPU/内存热点 |
| jstat/jmap |
JVM 诊断 |
GC/堆分析 |
| tcpdump |
网络抓包 |
网络层问题 |
| strace |
系统调用追踪 |
I/O 问题 |
17.3.3 Arthas 使用示例
# 安装 Arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 连接到目标 Java 进程
java -jar arthas-boot.jar
# 查看最繁忙的线程
thread -n 3
# 查看方法调用耗时
trace com.example.order.service.OrderService createOrder
# 查看方法调用栈
stack com.example.order.service.OrderService createOrder
# 查看对象属性
watch com.example.order.service.OrderService createOrder '{params, returnObj}' -x 2
# 反编译类
jad com.example.order.service.OrderService
# 监控方法调用(统计 QPS 和成功率)
monitor -c 5 com.example.order.service.OrderService createOrder
17.4 熔断与降级
17.4.1 熔断器模式回顾
熔断器状态转换:
正常流量 → CLOSED (所有请求通过)
│
│ 连续失败超过阈值 (如 5 次)
▼
OPEN (所有请求直接拒绝,返回降级响应)
│
│ 等待超时 (如 30 秒)
▼
HALF-OPEN (允许少量探测请求)
│
┌──────┴──────┐
│ │
探测成功 探测失败
│ │
▼ ▼
CLOSED OPEN
17.4.2 降级策略
| 策略 |
说明 |
示例 |
| 返回缓存数据 |
使用缓存中的旧数据 |
商品详情返回缓存 |
| 返回默认值 |
返回预设的默认值 |
推荐商品返回热门列表 |
| 功能降级 |
关闭非核心功能 |
关闭商品推荐 |
| 排队等待 |
请求排队,稍后处理 |
秒杀排队 |
| 友好提示 |
返回用户友好的错误信息 |
“系统繁忙,请稍后” |
17.4.3 Resilience4j 降级配置
@Service
public class OrderService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "createOrderFallback")
@Retry(name = "paymentService")
@TimeLimiter(name = "paymentService")
public CompletableFuture<Order> createOrder(CreateOrderCommand command) {
// 正常逻辑
PaymentResult result = paymentClient.createPayment(command);
return CompletableFuture.completedFuture(new Order(command, result));
}
// 降级方法
public CompletableFuture<Order> createOrderFallback(CreateOrderCommand command,
Exception e) {
log.warn("支付服务不可用,订单进入待支付状态", e);
// 降级策略:创建订单但标记为待支付
Order order = new Order(command, OrderStatus.PENDING_PAYMENT);
orderRepository.save(order);
// 发送延迟消息,稍后重试支付
messageQueue.send("payment-retry", new PaymentRetryMessage(order.getId()));
return CompletableFuture.completedFuture(order);
}
}
17.4.4 Resilience4j 配置
# application.yml
resilience4j:
circuitbreaker:
instances:
paymentService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
registerHealthIndicator: true
retry:
instances:
paymentService:
maxAttempts: 3
waitDuration: 500ms
retryExceptions:
- java.io.IOException
- java.net.SocketTimeoutException
ignoreExceptions:
- com.example.BusinessException
timelimiter:
instances:
paymentService:
timeoutDuration: 5s
cancelRunningFuture: true
17.5 常见问题速查表
17.5.1 服务调用类
| 问题 |
可能原因 |
排查方法 |
解决方案 |
| 服务调用超时 |
下游服务慢/网络问题 |
TraceID 追踪 |
优化下游/增加超时 |
| 连接拒绝 |
连接池满/服务未启动 |
netstat / 日志 |
扩大连接池 |
| DNS 解析失败 |
CoreDNS 异常/Service 不存在 |
nslookup |
检查 Service 配置 |
| 502 Bad Gateway |
Pod 未就绪/健康检查失败 |
K8s 事件/日志 |
修复健康检查 |
| 连接池耗尽 |
连接泄漏/并发太高 |
监控指标 |
修复泄漏/扩容 |
17.5.2 数据类
| 问题 |
可能原因 |
排查方法 |
解决方案 |
| 数据不一致 |
分布式事务失败 |
数据校验脚本 |
补偿/修复数据 |
| 缓存穿透 |
大量不存在的 key 查询 |
监控 + 日志 |
布隆过滤器/缓存空值 |
| 缓存雪崩 |
大量 key 同时过期 |
监控 |
随机过期时间 |
| 慢 SQL |
缺少索引/全表扫描 |
慢查询日志 |
添加索引/优化 SQL |
| 死锁 |
并发更新同一行 |
DB 死锁日志 |
优化事务/减少锁范围 |
17.5.3 资源类
| 问题 |
可能原因 |
排查方法 |
解决方案 |
| OOM |
内存泄漏/堆太小 |
Heap Dump 分析 |
修复泄漏/增加内存 |
| CPU 100% |
死循环/大量计算 |
top/Arthas |
优化代码 |
| 磁盘满 |
日志太多/临时文件 |
df -h |
日志轮转/清理 |
| 线程池耗尽 |
慢操作占用线程 |
Thread Dump |
异步化/扩容 |
17.6 故障演练清单
定期故障演练(建议每月一次):
┌───────────────────────────────────────────────────────────┐
│ 故障演练清单 │
├───────────────────────────────────────────────────────────┤
│ │
│ 基础设施层: │
│ □ 杀死一个服务 Pod → 验证自动重启 │
│ □ 杀死数据库主节点 → 验证主从切换 │
│ □ 注入网络延迟 500ms → 验证超时和熔断 │
│ □ 注入网络分区 → 验证服务降级 │
│ │
│ 应用层: │
│ □ 模拟支付服务不可用 → 验证订单降级 │
│ □ 模拟缓存全部失效 → 验证缓存穿透保护 │
│ □ 模拟消息队列堆积 → 验证消费追赶能力 │
│ □ 模拟数据库慢查询 → 验证连接池和超时 │
│ │
│ 业务层: │
│ □ 模拟秒杀流量 (10x) → 验证限流和排队 │
│ □ 模拟重复下单 → 验证幂等性 │
│ □ 模拟部分支付失败 → 验证补偿机制 │
└───────────────────────────────────────────────────────────┘
17.7 事后复盘(Postmortem)
17.7.1 复盘模板
# 故障复盘报告
## 基本信息
- **故障时间**: 2026-05-10 10:00 - 11:30 (90 分钟)
- **影响范围**: 订单服务不可用,影响约 30% 用户
- **严重程度**: P1
## 故障时间线
| 时间 | 事件 |
|------|------|
| 10:00 | 监控告警:订单服务 P99 延迟飙升到 5s |
| 10:05 | 值班人员确认问题,开始排查 |
| 10:15 | 定位到数据库慢查询 |
| 10:30 | 确认是全表扫描导致 |
| 10:45 | 紧急添加索引,准备上线 |
| 11:00 | 索引上线完成 |
| 11:30 | 性能恢复正常,故障结束 |
## 根因分析
订单表新增字段后,原有查询未更新索引,导致全表扫描。
## 修复措施
1. 紧急:添加缺失索引
2. 长期:SQL 变更需经过 DBA Review
## 改进项
| 改进项 | 负责人 | 截止日期 |
|--------|--------|----------|
| 添加慢 SQL 监控告警 | 张三 | 2026-05-20 |
| SQL 变更需 DBA Review | 李四 | 2026-05-15 |
| 补充索引覆盖度测试 | 王五 | 2026-05-25 |
⚠️ 注意事项
- 不要在故障中做大的改动——故障期间只做最小修复
- 保持冷静,按流程排查——恐慌会导致更多错误
- 及时沟通——故障期间保持团队和业务方的沟通
- 事后复盘不要追责——聚焦于流程和系统的改进
- 建立 Runbook——常见故障的处理手册
📖 扩展阅读
- Google SRE Book — 故障管理的最佳实践
- Arthas Documentation — Java 在线诊断工具
- Resilience4j — 弹性通信框架
- Site Reliability Engineering — O’Reilly SRE 实践
- The Phoenix Project — IT 运维小说(理解故障管理的重要性)
本章小结
| 要点 |
说明 |
| 调试方法 |
RED 方法:复现 → 探索 → 诊断 |
| 核心工具 |
TraceID 串联 + 链路追踪 + 日志聚合 |
| 性能排查 |
从宏观指标到微观线程的逐层分析 |
| 熔断降级 |
Resilience4j 实现熔断 + 降级策略 |
| 故障管理 |
定期演练 + 事后复盘 + Runbook |
📌 下一章:第 18 章:最佳实践 — 架构演进、组织架构、反模式总结。