← 返回场景决策
1. 看日志 — 第一排查手段
日志 = 飞机黑匣子
→
出事后回放记录,找出问题在哪
✅ 什么时候先看日志
- 接口报错:5xx 错误 — 先看服务端 ERROR 日志
- 业务逻辑异常:订单状态不对 — 搜索订单号关键字
- 定时任务失败:凌晨跑的任务没成功 — grep 任务名
- 数据不一致:金额对不上 — 搜索交易号看流程
📊 日志排查决策表
| 问题类型 | 看什么日志 | 关键字 | 工具 |
| 接口500 | 应用ERROR日志 | Exception / Caused by | grep/ELK |
| 慢接口 | 访问日志 | 耗时 > 阈值 | Nginx access.log |
| OOM | GC日志 + 堆dump | OutOfMemoryError | jmap |
| 死锁 | 线程dump | BLOCKED / waiting | jstack |
| MQ消费失败 | 消费者日志 | 消息ID + 异常 | MQ控制台 + 应用日志 |
💻 日志排查命令
# 实时看日志
tail -f /var/log/app/app.log
# 搜索异常关键字
grep -n "NullPointerException" app.log
# 搜索某个订单的所有日志
grep "ORDER-2024-001234" app.log
# 看最近10分钟ERROR日志
find /var/log/app -name "*.log" -mmin -10 | xargs grep "ERROR"
# ELK 查询(Kibana)
// level: ERROR AND message: "orderId: 1234"
// @timestamp: [now-30m TO now]
面试金句
grep + find 组合是 Linux 下日志排查的基本功:grep -n 带行号方便定位上下文,find -mmin -10 找最近 10 分钟修改的文件。ELK(Elasticsearch+Logstash+Kibana)将分散的日志聚合到统一搜索平台,Kibana 用 Lucene 查询语法(level:ERROR AND message:"orderId:1234")实现全文检索+时间范围过滤+字段匹配,比 grep 更高效地处理 TB 级日志。
2. 抓包 — 网络问题排查
抓包 = 路口装监控
→
每辆车从哪来到哪去,车上带了什么,全拍下来
✅ 什么时候抓包
- 接口不通:请求没到服务器还是服务器没返回 — 抓包看请求到底发出没
- 数据不对:接口返回值和预期不同 — 抓包看实际返回内容
- CORS 问题:浏览器报跨域错误 — 抓包看 OPTIONS 请求和响应头
- Cookie 问题:请求没带 Cookie — 抓包看请求头
- 第三方对接:微信/支付宝回调没收到 — 抓包验证
📊 抓包工具决策表
| 工具 | 层级 | 适合 | 难度 |
| Chrome DevTools | HTTP | 前端接口调试 | 低 |
| Charles/Fiddler | HTTP/HTTPS | 前后端联调、Mock | 低 |
| tcpdump | TCP/IP | 服务器网络排查 | 中 |
| Wireshark | 全协议 | 深入网络分析 | 高 |
# tcpdump 服务器抓包
# 抓 8080 端口的 HTTP 请求
tcpdump -i any port 8080 -A -s 0
# 抓某个IP的请求
tcpdump -i any host 10.0.0.5 -w capture.pcap
# 抓 HTTP POST 请求
tcpdump -i any -A -s 0 'tcp port 8080 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
面试金句
tcpdump 是服务器侧抓包的黄金标准:-i any 监听所有网卡,-A 以 ASCII 输出(方便看 HTTP 内容),-s 0 不截断包(完整捕获),-w file.pcap 保存到文件后用 Wireshark 图形化分析。生产环境用 tcpdump 抓包需注意性能影响(高流量时可能丢包),建议用 BPF 过滤器缩小范围(如 port 8080)。
3. 火焰图 — CPU性能排查
火焰图 = 城市天际线
→
越宽的建筑越占空间(越耗CPU),一眼看出哪里最"胖"
✅ 什么时候看火焰图
- CPU 飙高:top 看到某个 Java 进程 CPU 100% — 火焰图定位热点方法
- 接口延迟:某个接口响应慢 — 火焰图找到耗时方法
- GC 频繁:GC 占用大量 CPU — 火焰图看到 GC 热点
- 性能优化:找到优化点 — 哪个方法调用最多/最耗时
💻 生成火焰图
# 方式1:async-profiler(推荐,Java专用)
# 下载 async-profiler
java -jar async-profiler.jar -d 30 -f flamegraph.html <pid>
# 30秒后生成 flamegraph.html,浏览器打开即可
# 方式2:jstack 采样 + 生成
# 多次 jstack 统计方法出现频率
for i in {1..10}; do
jstack <pid> > stack_$i.txt
sleep 1
done
# 方式3:Arthas 生成火焰图
# java -jar arthas-boot.jar
# profiler start
# 等待30秒
# profiler stop --format html
面试金句
火焰图(Flame Graph)将大量调用栈采样可视化:横轴=方法在采样中出现的比例(越宽=越占 CPU),纵轴=调用深度(栈顶在最上方)。async-profiler 基于 perf_events / AsyncGetCallTrace,开销极低(<1%),不触发 safepoint 停顿,远优于 JFR/JStack 采样方式。"宽且浅"的方法是优化重点。
4. 线程dump — 线程问题排查
✅ 什么时候用线程dump
- 死锁:两个线程互相等待 — jstack 会自动报告 FOUND A JAVA LEVEL DEADLOCK
- 线程泄漏:线程数持续增长 — dump 看哪些线程在做什么
- 请求卡住:接口一直不返回 — dump 看处理线程在等什么
- CPU 高:先 top 找到高CPU线程,再 jstack 看对应线程栈
# 获取线程dump
jstack <pid> > thread_dump.txt
# 找 CPU 最高的线程
# Step 1: 找到Java进程
jps -l
# Step 2: 找到高CPU线程ID
top -Hp <pid>
# 假设线程ID是 12345
# Step 3: 转为16进制
printf "%x\n" 12345
# 输出: 3039
# Step 4: 在dump中搜索
jstack <pid> | grep "0x3039" -A 30
# 死锁示例输出:
# Found one Java-level deadlock:
# =============================
# "Thread-1":
# waiting to lock monitor...,- locked <0x000000076b...>(a java.lang.Object),
# which is held by "Thread-0"
# "Thread-0":
# waiting to lock monitor...,- locked <0x000000076b...>(a java.lang.Object),
# which is held by "Thread-1"
面试金句
top -Hp → printf %x → jstack grep 是排查 Java CPU 飙高的标准三步法:top -Hp(-H 显示线程级)找到 CPU 最高的 nid,转 16 进制后在 jstack 输出中搜索 nid=0x3039 对应的线程栈。⚠️ jstack 输出的 nid 是十六进制,top 显示的是十进制,必须转换。jstack 自动检测死锁并输出 Found one Java-level deadlock 提示。
5. 堆dump — 内存问题排查
✅ 什么时候用堆dump
- OOM:java.lang.OutOfMemoryError — 分析什么对象占满内存
- 内存泄漏:内存持续增长不释放 — 找到泄漏对象
- GC 频繁:Full GC 停顿长 — 看老年代被什么占满
# 手动获取堆dump
jmap -dump:format=b,file=heap.hprof <pid>
# OOM 时自动dump(推荐配置)
// JVM启动参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
# 用 MAT (Memory Analyzer Tool) 分析
# 1. 下载 Eclipse MAT
# 2. 打开 heapdump.hprof
# 3. 查看 Leak Suspects(泄漏嫌疑对象)
# 4. 查看 Dominator Tree(占内存最大的对象)
# 5. 查看 GC Roots 引用链(谁引用了这个对象导致无法回收)
# 快速查看堆概况(不用dump)
jmap -histo <pid> | head -20
# 输出类似:
# num #instances #bytes class name
# 1: 1234567 123456789 [B (byte数组)
# 2: 567890 45678901 java.lang.String
面试金句
-XX:+HeapDumpOnOutOfMemoryError 是生产必备 JVM 参数:OOM 发生时自动 dump 堆快照,事后用 MAT(Eclipse Memory Analyzer Tool) 分析。Dominator Tree 按对象持有内存大小排序("支配者树"),GC Roots 引用链展示泄漏对象通过什么路径被持有导致无法回收(如 ThreadLocal、静态集合、未关闭的 Connection)。jmap -histo 是无 dump 快速诊断:直接看对象计数 Top N。
6. Arthas — 线上诊断神器
Arthas = 远程B超
→
不用重启,不用改代码,直接看 JVM 内部
📊 Arthas 常用命令决策表
| 命令 | 功能 | 什么时候用 |
| dashboard | 全局面板(线程/内存/GC) | 快速了解 JVM 状态 |
| thread | 查看线程状态 | CPU高/死锁/线程卡住 |
| jad | 反编译类 | 确认线上代码版本 |
| watch | 观察方法调用 | 看入参/返回值/异常 |
| trace | 追踪方法调用链 | 接口慢,定位哪个方法耗时 |
| sc/search | 搜索类 | 确认类是否被加载 |
| heapdump | 导出堆dump | 内存问题 |
| profiler | 生成火焰图 | CPU性能分析 |
💻 Arthas 实战
# 启动 Arthas
java -jar arthas-boot.jar
# 选择你的 Java 进程
# 1. trace 追踪慢接口
trace com.example.service.OrderService createOrder
# 输出:
# +---[200ms] createOrder()
# +---[150ms] checkStock() ← 这里最慢!
# +---[30ms] saveOrder()
# +---[20ms] publishEvent()
# 2. watch 观察方法入参和返回值
watch com.example.service.OrderService createOrder "{params, returnObj, throwExp}" -x 2
# 每次调用都会打印入参、返回值、异常
# 3. 反编译确认代码版本
jad com.example.service.OrderService
# 看到源码 → 确认是不是最新版本
# 4. 条件追踪(只看特定订单)
trace com.example.service.OrderService createOrder 'params[0].orderId == "ORD123"'
面试金句
Arthas 通过 Java Agent + 字节码增强 实现运行时诊断,不重启、不改代码。trace 统计方法内部每个子调用的耗时(类似火焰图的时间维度版),watch 通过 OGNL 表达式 {params, returnObj, throwExp} 在方法返回时打印入参/返回值/异常,jad 反编译确认线上运行的 class 是否是最新的——经典排查场景是"明明改了代码为什么没生效"(往往是部署没更新或 classpath 冲突)。
7. 链路追踪 — 微服务调用链排查
链路追踪 = 快递物流追踪
→
你的包裹经过了哪些中转站,哪里卡住了
📊 链路追踪方案决策表
| 方案 | 语言 | 存储 | 适合 |
| SkyWalking | Java Agent | Elasticsearch | Java微服务首选 |
| Zipkin | 多语言 | 内存/ES/MySQL | Spring Cloud Sleuth |
| Jaeger | 多语言 | Elasticsearch/Cassandra | 云原生/K8s |
💻 SkyWalking 基本使用
# JVM 启动参数挂载 Agent
java -javaagent:/path/to/skywalking-agent.jar \
-Dskywalking.agent.service_name=order-service \
-Dskywalking.collector.backend_service=oap:11800 \
-jar app.jar
# 自动采集:
# 1. 每个 HTTP 请求的调用链
# 2. MySQL/Redis/MQ 调用
# 3. 接口耗时、错误率
# 4. 服务拓扑图
// 代码中添加自定义 Span
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
public void processOrder(Order order) {
// 获取 traceId,方便日志关联
String traceId = TraceContext.getTraceId();
log.info("traceId={}, orderId={}", traceId, order.getId());
}
面试金句
SkyWalking 通过 Java Agent 字节码增强实现零代码侵入的链路追踪:启动时 -javaagent 挂载,自动拦截 HTTP/MySQL/Redis/MQ 调用并注入 TraceId(传递通过 HTTP Header 的 sw8 协议)。TraceContext.getTraceId() 获取当前链路 ID,与日志关联后,一个 traceId 即可在 ELK 中串联整条微服务调用链——这是分布式系统从现象到根因的桥梁。
8. 排查思路 — 从现象到根因
📊 常见问题排查路线图
| 现象 | 第一步 | 第二步 | 第三步 |
| 接口500 | 看日志 ERROR | 搜索异常堆栈 | 找到出代码行修复 |
| 接口慢 | 看接口耗时日志 | trace定位慢方法 | 优化SQL/加缓存 |
| CPU飙高 | top -Hp找线程 | jstack看线程栈 | 死循环/正则回溯/加密 |
| 内存泄漏 | jmap -histo看对象 | MAT分析heap dump | 修复未关闭资源 |
| 死锁 | jstack报告死锁 | 看锁获取顺序 | 统一加锁顺序 |
| 网络不通 | ping/telnet测试 | tcpdump抓包 | 防火墙/路由配置 |
| MQ消费慢 | 看消费延迟 | 看消费者日志 | 消费逻辑优化/扩容 |
| 数据不一致 | 对比数据源 | 搜索日志找时间点 | 修复同步逻辑 |
💭
排查心法
- 先复现:能复现=解决了一半
- 先看日志:日志是成本最低的排查手段
- 缩小范围:从前端→网关→服务→数据库→网络,逐层排除
- 二分法:注释掉一半代码,看问题是否消失
- 对比法:正常环境 vs 异常环境,找差异
- 时间线:按时间线梳理,问题出现在哪个变更之后