人人都会AI编程

28.5 线上性能问题排查通用思路

更新时间:2026-07-11

线上性能问题往往来得突然:用户投诉响应慢、监控报警 CPU 飙升、接口超时率陡增。面对紧急情况,有一套清晰的排查思路比记住一百个命令更重要。本节梳理的并非特定工具的说明书,而是经过大量实战验证的排查流程和思维框架,能帮助你在压力下快速定位并解决问题。

28.5.1 第一步:明确现象,圈定时间范围

排查的第一件工作不是登机器看日志,而是把问题“问清楚”。

  • 确认影响面:是单个用户慢还是全量慢?是某个接口慢还是所有接口慢?前端 F12 网络面板的耗时分布(DNS、TCP、SSL、TTFB、下载)能初步判断瓶颈在浏览器、网络还是服务端。
  • 精确时间点:“昨天下午慢”这种描述没有价值。应从监控系统(Prometheus、Grafana、ELK 等)拉取接口响应时间、错误率、CPU/内存/磁盘 IO、JVM GC 等曲线,锁定问题开始和结束的准确时间段。
  • 关联变更:这个时间段有没有上线、发布、配置变更、流量突增(营销活动、爬虫、刷单)?大部分线上问题都能找到诱因,“最近改了什么”通常能直接给出答案。

28.5.2 第二步:快速止损,先恢复再追因

用户不会等你慢慢分析,所以必须第一时间决策是否需要回滚、限流或重启。

  • 回滚到上一个稳定版本:如果确认是新发布引入的问题,且修复时间不可控,果断回滚。对业务的影响远小于持续慢响应。
  • 临时限流/降级:通过网关(Nginx/Kong/Gateway)对高负载接口限流,或开启断路器降级非核心功能(如推荐、积分明细),释放资源保障核心链路。
  • 重启或切流:对于内存泄漏、死锁等无法立即修复的问题,重启能快速恢复;多实例下可通过摘除问题实例的流量,缩小影响范围。

止损后,保留现场证据(线程 dump、堆 dump、GC 日志、应用日志片段)用于事后分析。

28.5.3 第三步:自底向上,逐层定位瓶颈

线上系统是一层叠一层的结构,排查时遵循从基础设施到应用层的顺序,避免跳步猜测。

1. 网络与操作系统层

  • 检查网络延迟与丢包pingtelnetcurl -w 查看 TCP 连接耗时。如果跨机房、跨云,检查 DNS 解析和网络抖动。
  • 系统资源使用top 观察 CPU、内存、负载;iostat 查看磁盘 IO 利用率与等待时间;netstat -antp | grep ESTABLISHED 检查连接数。如果 CPU 等待 IO(wa)很高,多半是磁盘或网络 IO 瓶颈。
  • 带宽与端口:高并发场景下,注意文件描述符是否用尽(ulimit -n),TIME_WAIT 连接是否堆积。

2. JVM 层

大多数后端服务运行在 JVM 上,JVM 状态是重点。

  • GC 情况:通过 jstat -gcutil <pid> 1000 或 GC 日志观察,频繁 Full GC 或单次 GC 耗时过长(超过 200ms)会直接导致 STW,表现为接口间歇性超时。常见原因有堆内存过小、内存泄漏、对象分配过快。
  • 内存泄漏jmap -histo:live <pid> 可查看存活对象分布;如果老年代持续增长且 GC 后不下降,则可能存在泄漏。需要导出 heap dump(jmap -dump:format=b,file=heap.hprof <pid>),用 Eclipse MAT 或 JProfiler 分析大对象引用链。
  • 线程死锁与阻塞jstack <pid> 导出 thread dump,查看线程状态。大量 BLOCKED 线程等待同一把锁,定位到死锁;大量 RUNABLE 线程长期运行,可能是 CPU 密集计算;线程池耗尽(如无可用线程)表现为大批请求排队。

3. 中间件层——数据库、缓存、消息队列

  • 数据库慢查询:通过慢查询日志或数据库监控,锁定执行时间长的 SQL。分析执行计划,检查索引是否失效、全表扫描、锁等待等。关注连接池是否耗尽(active 连接数占满),一般由慢 SQL 或事务未提交导致。
  • 缓存问题:缓存击穿/雪崩导致大量请求直达数据库,检查缓存命中率。缓存服务器(Redis)自身 CPU/内存飙升、网络延迟,也会引发雪崩。
  • 消息队列堆积:消费者处理能力不足或消费阻塞,查看 Kafka/RabbitMQ 监控,分析消费 lag 增长趋势。

4. 应用层

排除底层问题后,再聚焦代码逻辑。

  • 接口耗时分布:在应用中增加方法级耗时埋点(如 Micrometer + Zipkin),或利用全链路追踪(SkyWalking、Jaeger)找到耗时最长的调用链。通常一个外部服务调用超时或不合理的循环嵌套是元凶。
  • 线程池配置:检查 Tomcat 线程池、异步任务线程池是否配置过小,导致请求排队。
  • 第三方依赖:调用外部 API 超时未设置合理的 connect/read timeout,线程会长时间挂起,耗尽连接池。

28.5.4 典型案例的排查路径

熟悉几个高频问题场景,能让你在接到报警时迅速形成排查假设。

场景一:CPU 使用率突然 100%

  • 步骤:top -H -p <pid> 找出占用 CPU 最高的线程 ID,转换为十六进制,在 jstack 结果中搜索该 tid,定位到具体线程的堆栈。常见原因:死循环、正则表达式回溯、大量 Groovy 脚本编译、不合理的递归、高并发下的序列化/反序列化。
  • 如果在 jstack 多次 dump 中,同一个线程一直停在相同位置,多半是该处代码的 CPU 密集计算。

场景二:内存持续增长,最终 OOM

  • 步骤:配置 JVM 启动参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,OOM 时自动 dump。用 MAT 分析 Leak Suspects,注意 ThreadLocal 未清理、静态集合无限增长、数据库 ResultSet 未关闭、自定义类加载器泄漏等典型问题。

场景三:接口间歇性超时,并发越高越严重

  • 步骤:检查线程池使用情况(jstack 中线程 WAITING 状态),检查是否有锁竞争(synchronized 代码块或 DB 行锁),检查 GC 暂停时间。很多间歇性超时是混合效应:高并发下线程池满 + 偶尔的 Full GC 一起作用。

28.5.5 善用在线诊断工具

生产环境不允许随意安装软件,推荐使用 Arthas(阿里开源)这类字节码增强工具,它可以在运行时动态观察,无需重启。

  • dashboard:实时查看系统的综合运行状态。
  • thread:查看线程列表和阻塞情况,thread -b 一键找出当前阻塞其他线程的线程。
  • trace:跟踪某个方法的调用路径和耗时,快速定位慢在哪个子调用。
  • watch:观察方法调用的入参、返回值、异常。
  • monitor:方法调用统计,查看 QPS、平均耗时、失败率。

此外,对于内存分析,JProfilerYourKit 的远程采样功能也能在低开销下获得对象引用关系。

28.5.6 复盘与预防

问题解决后,至少要完成三件事:

  • 根因文档:记录问题表现、排查路径、根本原因、解决方案和验证方式。这是团队经验的累积。
  • 监控补齐:如果某个瓶颈点事前没有报警,立即加上监控。比如慢 SQL 阈值告警、GC 频率与耗时告警、线程池队列积压告警、内存使用率趋势等。
  • 改进措施:是代码缺陷就加单元测试,是配置不合理就固化到配置中心,是容量不足就评估扩容或限流方案。避免同一个坑掉进去两次。

排查线上性能问题,本质是假设驱动的信息收集过程。每一次“为什么”都需要数据支撑,而不是凭直觉跳步。当这套思维内化为习惯后,面对任何突发状况,你都能有条不紊地解决问题,而不是被焦虑支配。