如何排查线上突然变慢的性能问题?

wen PHP项目 46

本文目录导读:

如何排查线上突然变慢的性能问题?

  1. 第一阶段:快速定位“是谁”变慢(全局视角)
  2. 第二阶段:深入查找“资源瓶颈”(系统层面)
  3. 第三阶段:深入代码逻辑(应用层面)
  4. 第四阶段:快速定位的“紧急止血”措施
  5. 一张排查流程图
  6. 关键心法:

排查线上服务突然变慢的性能问题,需要系统化、有步骤地进行,避免在慌乱中盲目操作,最有效的思路是遵循 “从外到内、自上而下、先排查关键指标后深入代码” 的原则。

以下是详细的排查步骤和工具指南,分为四个阶段:

第一阶段:快速定位“是谁”变慢(全局视角)

首先确认是整体系统都慢,还是某个接口、某台机器、某种类型用户慢。

  1. 查看监控大盘(黄金指标)

    • 流量:QPS/TPS(每秒请求/事务数)是否突然飙升(被攻击、业务被“薅羊毛”)或暴跌(服务不可用)。
    • 延迟:P50、P95、P99(第50、95、99百分位响应时间)的延迟曲线,如果P99飙升但P50正常,说明少数请求异常慢(可能被阻塞、长尾请求);如果P50也飙升,说明整体负载高
    • 错误率:HTTP 5xx(服务端错误)、超时、业务异常码是否增加。
    • 饱和度:CPU、内存、磁盘I/O、网络带宽的使用率是否接近100%。
  2. 确认变更时间线

    • 立即回看最近的代码发布、配置变更、数据库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)。

第三阶段:深入代码逻辑(应用层面)

当系统资源正常但依然慢时,问题出在业务代码逻辑或锁

  1. 抓取慢请求的调用链

    • 使用APM(应用性能监控)系统(如SkyWalking、Pinpoint、Jaeger、Datadog APM)查看慢请求的完整调用链路,哪一步(RPC调用、数据库查询、缓存查询)耗时最长?
    • 如果没有APM,在关键代码入口手动增加日志耗时统计(打印当前时间戳),分段排查。
  2. 数据库层(最常见的瓶颈)

    • 慢查询日志:开启数据库的慢查询日志(slow_query_log),抓到执行时间超过阈值的SQL语句。
    • EXPLAIN 分析:对慢SQL执行 EXPLAIN,检查 type(访问类型,如ALL全表扫描)、key(使用的索引)、rows(扫描行数)。
    • 锁竞争:查看数据库是否有死锁、行锁等待。SHOW PROCESSLIST 查看是否有大量 Waiting 状态的线程。
    • 连接池:应用连接池是否已耗尽(如HikariCP的Active连接数= MaximumPoolSize),导致请求排队等连接。
  3. 锁竞争(多线程)

    • 同步锁synchronizedReentrantLock 被高并发访问导致阻塞,堆栈中常见 parking to wait for
    • 分布式锁:Redis、ZooKeeper的锁竞争。
    • 使用 jstack 多次(间隔1秒)查看线程状态,如果有大量线程处于 BLOCKEDWAITING 状态,并且都等待同一个锁对象,那就是锁热点。

第四阶段:快速定位的“紧急止血”措施

如果系统正在严重故障,无法从容排查,可以尝试以下操作:

  1. 限流/降级
    • 对非核心业务进行降级(返回默认值或缓存数据)。
    • 在网关层(如Nginx、Spring Cloud Gateway)对异常流量进行限流(QPS限流)。
  2. 扩容/重启
    • 快速增加Pod实例或虚拟机(水平扩容)。
    • 紧急重启应用(能临时解决内存泄漏、死锁等“重启可恢复”问题,但需警惕重启可能导致数据丢失或雪崩)。
  3. 回滚:如果怀疑是最近一次发布导致,立即执行代码或配置回滚。

一张排查流程图

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%由“变化”引起(发布、流量、依赖方异常),不要一开始就深钻代码。
  • 数据驱动:不要猜测,用 topjstack慢查询日志 的数据说话。
  • 一个点一个点排除:从系统层面(CPU/内存) -> 中间件层面(数据库/缓存) -> 代码层面,逐层深入。

希望这份指南能帮你系统化地解决线上性能问题,如果有具体的报错信息或监控截图,也可以发出来,我可以帮你进一步分析。

抱歉,评论功能暂时关闭!