如何从报警开始反推到代码行

一句话速记

从报警到代码行的反推链路:报警信息(什么指标异常)→ 监控大盘(确认异常范围和趋势)→ 日志/Trace(找异常时间点的具体请求)→ TraceID(展开完整调用链)→ arthas 在线诊断(trace/watch 定位到方法/代码行)→ 代码定位(从方法名反查 git log 找最近的提交)。核心原则:每步都缩小排查范围,不要跳过中间步骤

通俗解释(5 分钟版)

报警:P99 延迟从 200ms 飙到 3000ms
  │
  ├─ 步骤 1(30 秒):看监控大盘,确认范围
  │   是单个接口慢 还是所有接口?→ 只有 /api/order/create 慢
  │   是所有实例慢 还是单个实例?→ 所有实例都慢
  │   从什么时候开始?→ 14:30 开始,正好是刚上线的时间
  │
  ├─ 步骤 2(1 分钟):找到异常 Trace
  │   打开 SkyWalking → 选 /api/order/create → 看 14:30 后的 trace
  │   → 找到慢的 trace → 展开:
  │      OrderService.createOrder() = 2800ms
  │        ├── validateStock() → InventoryService = 50ms  ✓
  │        ├── deductCoupon() → CouponService = 2700ms    ✗← 这里慢!
  │        │    └── matchRule() = 2680ms                  ✗← 这个方法
  │
  ├─ 步骤 3(1 分钟):定位到代码行
  │   arthas attach 到订单服务
  │   arthas> trace com.example.service.CouponService matchRule -n 5
  │   → 看到 matchRule() 内部:
  │      Pattern.compile() = 2500ms ← 正则编译/匹配在这一步
  │      ...
  │
  ├─ 步骤 4(1 分钟):反查代码
  │   git log --since="2024-01-15" --until="2024-01-16" -- src/main/java/com/example/service/CouponService.java
  │   → commit abc123 在今天 14:28 改了 matchRule() 的正则
  │   → 看 diff → 某营销同事加了一个复杂正则规则
  │
  └─ 结论:新上线的正则规则导致回溯,回滚这个 commit 或优化正则

关键细节

步骤 1:从报警信息定界

不同报警源的排查起点

报警类型                  第一眼看什么                 下一步
──────────────────────────────────────────────────────────
P99 延迟飙升              监控大盘:按接口维度看       哪一个接口慢
                         是单个接口还是全局?

QPS 突降                 监控大盘:是入口 QPS 降       上游限流了?
                         还是处理的 QPS 降?            自己处理不过来?

错误率飙升               监控大盘:错误码分布           4xx(参数)→ 新上线改了什么
                         (4xx / 5xx)                   5xx(服务内部)→ 看日志

CPU / 内存飙升            arthas dashboard             thread -n 5 找 CPU 线程
                                                      memory 看内存分布

Pod OOMKilled             事件日志 + JVM 参数          是否 HeapDumpOnOutOfMemoryError
                         kubectl describe pod           dump 文件在哪里

DB 连接池耗尽             DB processlist              谁在长时间占用连接
                         SHOW PROCESSLIST              有没有慢查询

定界四问

1. 影响范围:所有实例还是个别实例?→ 个别 → 实例级别问题(网络/磁盘/GC)
2. 时间范围:从什么时候开始的?→ 看部署时间线(最近一次上线/配置变更)
3. 影响接口:所有接口还是某个接口?→ 某个 → 查该接口的调用链
4. 影响用户:所有用户还是特定用户?→ 特定 → 看用户数据是否触发边界 case

步骤 2:从 Trace/Log 找异常请求

有全链路追踪

SkyWalking / Zipkin → 选慢接口 → 筛选时间范围
→ 按 duration 降序 → 找到最慢的 trace
→ 展开 span 树 → 找到耗时占比最大的 span
→ 点进去看 span 的 tags/logs:
   可能包含:SQL 语句、RPC 入参、异常堆栈、方法名

只有日志(没有全链路追踪)

# 1. 根据报警时间圈定日志时间范围
grep "2024-01-15 14:30" /var/log/app.log | grep "ERROR\|WARN"
 
# 2. 按 TraceID 聚合请求
grep "abc123" /var/log/app.log | sort -k1  # 按时间排序
# 手动串出一个请求的完整生命周期
 
# 3. 找耗时长的方法
grep "2024-01-15 14:3[0-5]" /var/log/app.log | \
  grep "execute\|invoke\|call" | \
  awk '{print $NF}' | sort -rn | head -20
# 假设最后一列是耗时

步骤 3:arthas 在线定位方法耗时

# 反编译确认类和方法签名(不需要源码)
jad com.example.service.CouponService
 
# 追踪方法调用树和每步耗时(最常用)
trace com.example.service.CouponService matchRule -n 5
# -n 5:只追踪 5 次(避免追踪太久影响生产)
 
# 输出示例:
# `---ts=2024-01-15 14:30:25;thread_name=http-nio-8080-exec-3;id=2a;is_daemon=true;priority=5
#     `---[2680.5ms] com.example.service.CouponService:matchRule()
#         +---[0.01ms] com.example.rule.RuleEngine:loadRules()
#         `---[2680.0ms] java.util.regex.Pattern:matches()     ← 正则匹配 2.68s!
#             `---[2680.0ms] java.util.regex.Pattern$Curly:match()
 
# 查看方法入参和返回值
watch com.example.service.CouponService matchRule '{params,returnObj}' -x 3
# 可以看到每次调用的入参 → 找到导致慢的特定输入

步骤 4:反推代码变更

# 找到方法所在的文件
# 从 arthas jad 输出知道类名 → 推断文件路径
find . -name "CouponService.java"
 
# 看最近的变更
git log --oneline -10 -- src/main/java/com/example/service/CouponService.java
 
# 看具体改了什么
git diff HEAD~1 -- src/main/java/com/example/service/CouponService.java
 
# 看是谁改的、什么时候改的
git log --follow -p -- src/main/java/com/example/service/CouponService.java | head -100
 
# 如果确定了某个 commit 是问题引入点
git show abc123

完整排查决策树

报警
  │
  ├─ 是刚上线后马上出现的?
  │   → git log 看最近 1 小时的上线记录
  │   → 直接怀疑这次上线引入的 → 回滚验证
  │
  ├─ 是逐渐变慢的?
  │   → 怀疑数据量增长/泄漏 → jstat -gc 看 Old 区趋势
  │   → 怀疑慢查询 → DB processlist
  │   → 怀疑连接泄漏 → ss -tan state close-wait + leakDetectionThreshold
  │
  ├─ 是周期性出现的?
  │   → 怀疑定时任务/定时 GC → 对比时间线和 GC 日志/crontab
  │
  └─ 是突发性的(个别请求慢)?
      → 全链路追踪找慢 trace → arthas trace/watch → 定位方法

延伸追问

  • Q:事后排查,日志已经被轮转删了怎么办? → 提前把日志打到 ELK/Loki/SLS 等集中式日志平台,设好保留期(至少 7 天)。如果日志真的丢了 → 靠监控指标回顾(Prometheus metrics 通常保留更久),至少能知道”什么时间、什么接口、什么指标异常”。
  • Q:trace/watch 在生产环境安全吗? → arthas trace 有性能开销(约 5-10%),建议:① 加 -n 限制次数;② 只 trace 慢方法而非入口方法;③ 排查完马上下线(stopreset)。watch 同理,不要对高频方法大量 watch。
  • Q:怎么证明”就是这次上线导致的”? → 最硬证据:回滚后问题消失 → 确定是这次上线的代码引入。辅助证据:① 监控图上看指标突变时间 = 上线时间;② git diff 找到可疑代码变更;③ arthas trace 证实该代码确实慢。

我的记法

  • 四步反推:报警信息(定范围)→ 监控/Trace(找慢请求)→ arthas trace(定位方法)→ git log(找变更)
  • 定界四问:影响范围?什么时间开始?哪个接口?哪个用户?
  • 最有力证据:回滚后问题消失 = 100% 确定是这次上线
  • 一句话:「从报警到代码行,每步都在缩小包围圈,别跳步,跳步 = 猜」

状态

  • 已背速记
  • 能讲通俗版
  • 能答追问