本文目录导读:

排查线上服务突然变慢的性能问题,需要系统化、有步骤地进行,避免在慌乱中盲目操作,最有效的思路是遵循 “从外到内、自上而下、先排查关键指标后深入代码” 的原则。
以下是详细的排查步骤和工具指南,分为四个阶段:
第一阶段:快速定位“是谁”变慢(全局视角)
首先确认是整体系统都慢,还是某个接口、某台机器、某种类型用户慢。
-
查看监控大盘(黄金指标):
- 流量:QPS/TPS(每秒请求/事务数)是否突然飙升(被攻击、业务被“薅羊毛”)或暴跌(服务不可用)。
- 延迟:P50、P95、P99(第50、95、99百分位响应时间)的延迟曲线,如果P99飙升但P50正常,说明少数请求异常慢(可能被阻塞、长尾请求);如果P50也飙升,说明整体负载高。
- 错误率:HTTP 5xx(服务端错误)、超时、业务异常码是否增加。
- 饱和度:CPU、内存、磁盘I/O、网络带宽的使用率是否接近100%。
-
确认变更时间线:
- 立即回看最近的代码发布、配置变更、数据库DDL(数据定义语言,如加索引)、第三方依赖升级、流量切换,90%的性能问题源于变更。
- 检查上下游依赖服务是否正常(如调用外部API、Redis、MySQL、消息队列),如果下游变慢,你的服务也会“被变慢”。
第二阶段:深入查找“资源瓶颈”(系统层面)
如果确认是服务自身变慢,登录一台有问题的实例,检查操作系统和中间件。
CPU 使用率过高
- 现象:
top命令显示 CPU 的us(用户态)或sy(内核态)接近100%。 - 排查工具:
top/htop:找到占用CPU最高的进程PID。top -H -p <PID>:查看该进程内最耗CPU的线程TID。printf "%x\n" <TID>:将线程ID转换为十六进制(nid)。jstack <PID> | grep -A20 <nid>:在Java线程堆栈中定位到具体代码行。
- 常见原因:死循环、过度的GC(垃圾回收)、正则表达式回溯、序列化/反序列化瓶颈。
内存问题(GC频繁 / OOM)
- 现象:应用响应变慢,
top看到 CPU 的si(软中断)或sy较高(GC线程占用),或者直接OOM(内存溢出)。 - 排查工具:
jstat -gcutil <PID> 1000:每秒查看GC情况,关注 YGC(年轻代GC)/ FGC(完全GC)频率 和耗时,如果FGC频繁且耗时长,STW(Stop-The-World,暂停所有用户线程)会导致严重停顿。jmap -heap <PID>:查看堆内存配置和占用。jmap -dump:live,format=b,file=heap.hprof <PID>:导出堆转储文件(注意会暂停服务,慎用,建议在备用机或低峰期执行)。
- 常见原因:内存泄漏、缓存设置过大、大对象分配过多。
I/O 瓶颈(磁盘或网络)
-
现象:
top命令中wa(CPU等待I/O完成的时间)很高;iostat -x 1显示%util(设备利用率)接近100%或await(平均I/O请求处理时间)很大。 -
排查工具:
iostat -x 1:监控磁盘读写延迟和队列长度。dstat:综合查看CPU、磁盘、网络、系统负载。netstat -s:查看网络丢包、重传率。
-
常见原因:
- 数据库:慢SQL(结构化查询语言)、未命中索引、锁等待。
- 日志:短时间内疯狂打印错误日志(Log4j/SLF4J)。
- 磁盘:磁盘空间满(
df -h)、文件句柄耗尽(lsof -p <PID> | wc -l)。
第三阶段:深入代码逻辑(应用层面)
当系统资源正常但依然慢时,问题出在业务代码逻辑或锁。
-
抓取慢请求的调用链:
- 使用APM(应用性能监控)系统(如SkyWalking、Pinpoint、Jaeger、Datadog APM)查看慢请求的完整调用链路,哪一步(RPC调用、数据库查询、缓存查询)耗时最长?
- 如果没有APM,在关键代码入口手动增加日志耗时统计(打印当前时间戳),分段排查。
-
数据库层(最常见的瓶颈):
- 慢查询日志:开启数据库的慢查询日志(
slow_query_log),抓到执行时间超过阈值的SQL语句。 EXPLAIN分析:对慢SQL执行EXPLAIN,检查type(访问类型,如ALL全表扫描)、key(使用的索引)、rows(扫描行数)。- 锁竞争:查看数据库是否有死锁、行锁等待。
SHOW PROCESSLIST查看是否有大量Waiting状态的线程。 - 连接池:应用连接池是否已耗尽(如HikariCP的
Active连接数=MaximumPoolSize),导致请求排队等连接。
- 慢查询日志:开启数据库的慢查询日志(
-
锁竞争(多线程):
- 同步锁:
synchronized、ReentrantLock被高并发访问导致阻塞,堆栈中常见parking to wait for - 分布式锁:Redis、ZooKeeper的锁竞争。
- 使用
jstack多次(间隔1秒)查看线程状态,如果有大量线程处于BLOCKED或WAITING状态,并且都等待同一个锁对象,那就是锁热点。
- 同步锁:
第四阶段:快速定位的“紧急止血”措施
如果系统正在严重故障,无法从容排查,可以尝试以下操作:
- 限流/降级:
- 对非核心业务进行降级(返回默认值或缓存数据)。
- 在网关层(如Nginx、Spring Cloud Gateway)对异常流量进行限流(QPS限流)。
- 扩容/重启:
- 快速增加Pod实例或虚拟机(水平扩容)。
- 紧急重启应用(能临时解决内存泄漏、死锁等“重启可恢复”问题,但需警惕重启可能导致数据丢失或雪崩)。
- 回滚:如果怀疑是最近一次发布导致,立即执行代码或配置回滚。
一张排查流程图
graph TD
A[用户反馈/告警:系统变慢] --> B{查看监控大盘:CPU/内存/I/O/延迟/错误率?}
B -- “CPU高” --> C[top -> jstack 抓线程堆栈]
B -- “内存高/GC频繁” --> D[ jstat/jmap 分析堆栈]
B -- “磁盘I/O高” --> E[ iostat -> 定位是日志还是数据库]
B -- “网络/连接数高” --> F[ netstat/APM 调用链]
B -- “所有指标正常” --> G[深入应用层]
G --> G1[抓慢查询日志 -> EXPLAIN SQL]
G --> G2[抓线程堆栈 -> 查锁竞争/死锁]
G --> G3[检查日志打印频率]
C --> H[定位到具体代码行/线程]
D --> H
E --> I[数据库慢SQL优化/日志降级]
F --> J[检查依赖服务/第三方API超时]
G1 --> I
G2 --> K[优化锁粒度/使用无锁结构]
H --> L[修复/优化代码]
I --> L
J --> L
K --> L
L --> M[观察监控,确认恢复]
关键心法:
- 先看变化,再分析状态:性能问题90%由“变化”引起(发布、流量、依赖方异常),不要一开始就深钻代码。
- 数据驱动:不要猜测,用
top、jstack、慢查询日志的数据说话。 - 一个点一个点排除:从系统层面(CPU/内存) -> 中间件层面(数据库/缓存) -> 代码层面,逐层深入。
希望这份指南能帮你系统化地解决线上性能问题,如果有具体的报错信息或监控截图,也可以发出来,我可以帮你进一步分析。