网络 / 应用 / DB / 下游四层慢的分层排查
一句话速记
接口变慢,先定位是哪一层:网络层(丢包/重传/带宽 → ss -i / ping -c 100 看延迟分布);应用层(CPU 高/GC 频繁/线程池满/锁竞争 → arthas dashboard / thread -n 5);DB 层(慢查询/锁等待/连接池耗尽 → SHOW PROCESSLIST + slow log);下游层(调用超时/熔断 → 全链路追踪看 span 耗时分布)。逐层二分法定界,先排除网络和 DB,再查应用和下游。
通俗解释(5 分钟版)
用户投诉「页面打不开」→ 排查路径:
网络层 ping/telnet 通不通?延迟多少?
↓ 通
应用层 请求进来了吗?进来的耗时多少?CPU/GC 正常吗?
↓ 请求进来了
DB 层 慢查询吗?锁等待吗?连接池满了吗?
↓ DB 正常
下游层 调了哪些下游?哪个下游慢?还是下游熔断了?
↓ 下游正常
→ 继续深挖:序列化慢?日志刷盘慢?磁盘 IO?
核心原则:永远先排除最外层(网络),然后按请求流经路径逐层排查。不要上来就猜”是不是 GC 导致的”——先用数据说话。
关键细节
第一层:网络(10 秒确认)
# 1. 基本连通性
ping -c 100 <host> # 看丢包率(> 0.1% 就得查)和延迟分布
mtr -r -c 100 <host> # 看每一跳的延迟和丢包(比 ping 更全面)
# 2. TCP 连接状态
ss -s # 总连接数概览
ss -tan state time-wait | wc -l # TIME_WAIT 数量
ss -tan state close-wait | wc -l # CLOSE_WAIT 数量(> 100 → 应用没 close socket)
ss -i # 看每个连接的重传/拥塞窗口
# 3. 网卡流量
sar -n DEV 1 10 # 查看网络吞吐,看是否打满网卡带宽
iftop -i eth0 # 实时看哪个 IP 流量最大
# 4. DNS
nslookup <domain> # DNS 解析是否慢
# 应用层 DNS 缓存没配 → 每次调用都解析 → 累积几百 ms网络层排查 30 秒结论:
ping 丢包 > 1% → 网络问题,联系运维/云厂商
TIME_WAIT > 5000 → 短连接太多,改长连接或调内核参数
CLOSE_WAIT > 100 → 应用层 socket 泄漏(没 close)
网卡流量接近带宽上限 → 加带宽或限流
第二层:应用(最复杂)
# 1. 快速看整体
arthas> dashboard
# 关注:CPU 使用率、GC 次数/耗时、线程数、内存使用率
# 2. 找 CPU 最高线程
arthas> thread -n 5
# 如果某个线程长期 CPU 最高 → 可能是死循环或计算密集
# 如果 CPU 都不高但接口慢 → 可能是 IO 等待/锁竞争
# 3. 看锁竞争
arthas> thread -b # 找 BLOCKED 线程
# BLOCKED 线程多 → synchronized 锁竞争 → 并发能力下降
# 4. 看 GC 停顿
jstat -gc <pid> 1s 20
# FGCT 持续增长 + GC 频繁 → GC 占用了大量 CPU 时间
# 接口 P99 慢 + 与 GC 时间线吻合 → GC 是根因
# 5. 看线程池
arthas> thread | grep pool | wc -l # 线程池线程数
# 线程数接近 maxPoolSize → 线程池打满,新请求在排队
# arthas> thread --state BLOCKED | grep -c "pool" → 线程池中有多少在等锁应用层快速判断矩阵:
观察到的现象 可能的根因 下一步
───────────────────────────────────────────────────────────
CPU 高(> 80%) 死循环/计算密集 thread -n 3 找热点方法
GC 频繁 + 停顿时间长 堆内存不够/泄漏 jstat -gc 验证 + dump
BLOCKED 线程多 锁竞争 thread -b 找死锁/阻塞
WAITING 线程多 > 正常空闲 线程池打满/下游慢 trace 看哪步慢
CPU 正常但接口慢 等待 IO/网络/DB 看下一步
第三层:DB
-- 1. 找当前慢查询
SHOW FULL PROCESSLIST;
-- 关注:Time > 5s 的查询、State = 'Sending data'/'Creating sort index'/'Locked'
-- 2. 看锁等待
SELECT * FROM information_schema.INNODB_TRX; -- 当前事务
SELECT * FROM information_schema.INNODB_LOCKS; -- 当前锁
SELECT * FROM information_schema.INNODB_LOCK_WAITS; -- 锁等待关系
-- 3. 看连接数
SHOW STATUS LIKE 'Threads_connected'; -- 当前连接数
SHOW VARIABLES LIKE 'max_connections'; -- 最大连接数
-- Threads_connected / max_connections > 0.7 → 连接池或连接泄漏问题
-- 4. 慢查询日志分析
-- 用 pt-query-digest 分析 slow log
pt-query-digest /var/log/mysql/slow.log
-- 关注:Query_time 分布、Rows_examined 很大的查询(没走索引)DB 层典型问题和对策:
慢查询 加索引 / 优化 SQL / 分库分表
锁等待(Lock wait timeout) 捋事务顺序 / 缩短事务 / 减小锁定范围
连接池满 检查连接泄漏 / 增大连接池 / 加只读实例
CPU 高 可能是慢查询导致排序/临时表 → 查 processlist
第四层:下游
# 1. 全链路追踪(SkyWalking / Zipkin / Jaeger)
# 找出耗时最长的 span → 定位到具体下游接口
# 2. 没有全链路?查日志
grep "调用.*超时\|timeout\|connect.*refused" /var/log/app.log | tail -100
# 3. 看熔断状态
# Sentinel / Hystrix 的 dashboard 看熔断器是否打开
# 熔断打开 → 下游已经挂了,快速失败是正常的下游排查清单:
下游接口 P99 是多少? → 全链路追踪 span 耗时
下游有没有超时/熔断? → 监控 dashboard
超时时间设得合理吗? → 你的 timeout 和下游 P99 的关系
有没有重试?重试是否放大了请求? → 重试策略检查
连接池是否耗尽?(http client pool) → 同应用层线程池排查
完整排查流程总结
用户:接口 P99 从 200ms 涨到 3s
1. 网络(10 秒,先排除)
ping/mtr → 正常(排除网络)
2. 应用(2 分钟,看仪表盘)
dashboard → CPU 30% 正常
jstat -gc → Full GC 不频繁
thread -n 5 → CPU 不高,没死循环
thread -b → 没有 BLOCKED
→ 不太像应用自身问题
3. DB(1 分钟)
SHOW PROCESSLIST → 发现 5 个查询 State=Sending data, Time>10s
→ DB 层!某个慢查询拖慢了整个接口
explain → 没走索引,全表扫描 200w 行
4. 修复
加索引 → P99 回到 200ms
延伸追问
- Q:四层都没问题,但接口就是慢?
→ 查日志框架(logback 同步写盘可能 100ms+)、序列化(大对象 JSON 序列化)、安全框架(每次请求查 DB 鉴权)、过滤器链(某个 Filter 做了耗时操作)。用 arthas
trace追每一层耗时。 - Q:应用层 CPU 不高但接口慢,最常见原因?
→ IO 等待(等 DB/等下游/等网络),线程在 WAITING 状态而非 RUNNABLE。看 jstack 的 WAITING 线程比例,arthas
trace看哪步 IO 耗时最大。 - Q:网络排查中 MTR 比 ping 好在哪? → ping 只看端到端,MTR 看每一跳。如果第 3 跳丢包 30% 但后面正常 → 可能只是那台路由器不响应 ICMP。如果最后 3 跳同步丢包 → 链路上有真实丢包。
我的记法
- 四层法:网络(ping/ss/mtr)→ 应用(dashboard/thread/jstat)→ DB(PROCESSLIST/locks)→ 下游(全链路 span)
- 核心原则:逐层二分法定界,先排除最简单的(网络),再查最复杂的(应用)
- 快速定位:70% 的慢接口问题在 DB 层(慢查询/锁/连接池),20% 在下游,10% 在应用/网络
- 一句话:「从外到内逐层排查,拿着数据说哪层慢,别猜」
状态
- 已背速记
- 能讲通俗版
- 能答追问