微服务拆分精讲 / 第 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 章:最佳实践 — 架构演进、组织架构、反模式总结。