JVM、线程、SQL 与网络联合排障
一次 Java 接口变慢可能发生在入口排队、线程池、GC、锁、数据库或网络中的任一阶段。排障应先用时间和影响范围缩小链路,再从同一请求的证据验证原因,避免看到某个高指标就直接调参数。
1. 先固定时间、范围和变更
确认异常从何时开始,影响全部接口还是单一路由、全部实例还是一个区域、所有用户还是特定租户。记录错误码、P50/P99、吞吐和最老请求。
把发布时间线、配置版本、流量变化和依赖事件放在同一窗口。若只有新版本实例变慢,优先比较版本;若全部服务同时异常,先检查共享数据库、网络和入口。
止血与取证并行:可以限流、摘除异常实例或暂停发布,但至少保留一个安全样本的线程栈、JFR、GC 日志和关键指标,避免重启后证据消失。
2. 从入口分解端到端耗时
比较客户端、网关和服务端总耗时,再用 Trace 找出 queue、application、SQL 和 downstream span。服务端业务只用 20 ms,而网关显示 2 s,问题可能在连接池等待、网络或重试。
检查请求量和在途并发。CPU 正常但 P99 升高,经常表示线程在等待锁、连接、磁盘或网络,而不是没有资源问题。
3. JVM 证据区分暂停、分配和原生资源
观察 GC pause、分配速率、堆占用、晋升、类加载、线程数、direct memory 和进程 RSS。高堆使用本身不等于泄漏;关键是回收后基线是否持续上升,以及暂停是否与延迟时间吻合。
JFR 可以把 GC、分配、锁、Socket、文件 I/O 和 CPU profile 放在同一时间线。容器 OOMKilled 可能没有 Java OOM,需比较 heap、native memory、线程栈与容器 limit。
不要在未评估停顿和磁盘空间时直接抓 live heap dump。先用 histogram、NMT 和短 JFR 缩小范围。
4. 线程栈显示执行与等待位置
连续采集多份 thread dump,比较同一线程是否长期停在相同栈:
- 大量
BLOCKED指向 monitor 竞争。 - 线程池 worker 全忙且队列增长,说明处理能力不足或任务变慢。
- 大量线程等待 JDBC 连接,继续增加业务线程只会扩大排队。
- RUNNABLE 不一定占 CPU,也可能在 native I/O。
- 虚拟线程 pinning、载体线程和外部连接仍需单独检查。
线程名包含池和业务用途,才能把栈与 active、queue、reject 指标对应起来。
5. 数据库同时检查执行、等待和计划
应用侧记录连接池 pending、事务持有时间和 SQL span;数据库侧检查当前语句、慢查询、锁等待、扫描行数、缓冲命中和 CPU/I/O。
SQL 变慢可能来自:
- 执行计划因统计信息或参数分布变化。
- 新索引缺失或选择性下降。
- 长事务与锁等待。
- 连接数增加造成数据库过载。
- N+1 或重试让调用次数上升。
EXPLAIN ANALYZE 验证实际行数和耗时,但对生产重 SQL 要评估执行影响。只看到“使用了索引”不能排除大量扫描与回表。
6. 网络排查连接与重传
观察 DNS、连接池等待、TCP connect、TLS、首字节和读取耗时,以及连接 reset、重传、丢包和各节点延迟。长连接可能仍指向摘流实例,NAT 和负载均衡也可能有空闲超时。
单次 ping 不能代表应用 TCP 路径、端口或长时间分布。结合客户端连接指标、服务端 accept/连接数、代理日志和必要的抓包确认。
HTTP/2 单连接丢包会影响多个 stream;连接池过小则请求在客户端等待,服务端完全看不到慢请求。
7. 用时间相关性建立并验证假设
例如 P99 上升同时出现:JDBC pending 增长、数据库 lock wait 增长、多个线程栈停在同一查询。这个证据链比“数据库 CPU 高”更接近原因。
每次只改变一个主要变量,在部分实例或可重复压测中验证。增加线程池、连接池和 timeout 可能暂时降低拒绝,却把更多工作推给已饱和下游。
修复后检查原始 SLI、资源等待和业务结果,并在相同负载下比较;没有量化前后结果,不能确认根因已经解决。
8. 形成可复用的取证能力
生产实例预先启用可控 GC 日志、JFR 配置、heap dump 路径和诊断权限,仪表盘连接服务版本、线程池、连接池、SQL 与网络阶段。Runbook 记录安全命令、证据保存和升级联系人。
故障结束后补充能更早发现根因的指标和回归测试,不把一次性的手工命令当成长期治理。
9. 常见问题
9.1 P99 升高但 CPU 不高,先看什么
先看在途并发、线程池队列、连接池等待和 Trace 慢 span,再检查锁、数据库和网络。等待型瓶颈不会表现为高用户态 CPU。
9.2 重启后恢复是否说明是 JVM 问题
不能证明。重启同时清除了连接、线程、缓存、实例流量和本地状态,也可能暂时避开数据库锁或坏节点。应保留重启前证据,并观察哪个状态随重启被重置。
10. 面试题
10.1 接口 P99 升高但 CPU 正常,如何联合排查
出现公司:欢聚时代
考察重点
- 影响范围、发布时线和分段耗时。
- JVM、线程池、连接池、SQL 与网络证据。
- 止血、取证、假设验证和修复回归。
相关内容:第 1 节“先固定时间、范围和变更”至第 8 节“形成可复用的取证能力”。
参考回答
先确认异常时间、路由、实例和版本,比较请求量、错误和 P99;同时限流或摘除异常实例止血,并保留线程栈、JFR 和数据库现场。用网关与 Trace 分解连接等待、服务处理、SQL 和下游耗时。CPU 正常更要看线程池队列、JDBC pending、锁等待和网络,因为线程可能都在等待。
JVM 侧关联 GC pause、分配、线程和 native memory;连续 thread dump 看是否卡在同一锁、池或调用;数据库侧看运行 SQL、锁、实际执行计划;网络侧看 DNS、建连、首字节、重传和连接池。形成跨层时间相关后只改一个变量验证,修复后用同负载下的原始 SLI 和等待指标确认,不能仅以重启恢复作为根因证据。