Linux生产环境CPU 100%问题排查实战:从发现到定位的完整复盘(2026)

📝 648 字 · ☕ 2 分钟阅读

凌晨两点的告警电话

手机震了三下我才醒。一看,运维群炸了——“线上接口全部超时!”“CPU 100%了!”“用户投诉爆了!”。

这种事情干过运维的都懂:你半梦半醒地打开电脑,一边祈祷是监控误报,一边连 VPN。然后 top 一敲,心就凉了半截——load average 三位数,CPU 全部飙红。

这篇文章就是我从几十次线上 CPU 飙高事故中总结出来的排查方法论。不是教科书,是真刀真枪干出来的。每一步都有具体命令、具体输出、具体判断标准。你下次遇到 CPU 告警,按这个流程走,十分钟定位、半小时修复。

第一步:快速确认——是不是真的 CPU 满了

别上来就瞎操作。先确认三件事:负载高不高、CPU 利用率多少、I/O 有没有问题。因为有些“CPU 高”其实是 I/O wait,方向完全不一样。

$ uptime
 02:14:23 up 47 days,  3:12,  2 users,  load average: 28.51, 24.33, 18.67

这台机器是 16 核,load average 28 意味着有 12 个进程在排队等 CPU。不是 I/O wait 的问题,是真的 CPU 吃满了。

$ top -bn1 | head -5
top - 02:14:35 up 47 days,  3:13,  2 users,  load average: 28.51, 24.33, 18.67
Tasks: 342 total,   4 running, 338 sleeping,   0 stopped,   0 zombie
%Cpu(s): 92.3 us,  5.1 sy,  0.0 ni,  2.0 id,  0.0 wa,  0.0 hi,  0.6 si,  0.0 st

关键看这几个指标:us(用户态 CPU)92.3% → 不是内核问题,是应用代码在烧 CPUwa(I/O wait)0.0% → 不是磁盘问题。id(idle)只剩 2% → CPU 确实没余量了。

判断口诀:us 高 → 查应用代码;sy 高 → 查系统调用/kernel;wa 高 → 查磁盘/网络 I/O;si 高 → 查中断。

第二步:定位元凶进程

接下来找出哪个进程在吃 CPU:

$ top -bc -n1 | head -18

  PID USER      %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
28342 app       89.2  2.3 4523600 376512 ?     Sl   01:23  42:15 /usr/local/bin/java -jar app.jar
 1289 app        4.0  1.1 2104568 184224 ?     Sl   Jun13  32:10 /usr/local/bin/python3 /app/worker.py
 3012 root       2.3  0.1  25688  1708 ?        Ss   Jun13  18:22 /usr/sbin/nginx

PID 28342 的 Java 进程占了 89.2% CPU——它就是元凶。记录下来,下一步对它做手术。

hot tip:top -bc 而不是 top -b,区别是 -c 会显示完整命令行,你能一眼看出是哪个服务、哪个 jar 包、甚至启动参数。

第三步:线程级定位——搞清楚是谁在烧 CPU

一个 Java 进程里可能有几百个线程,不能“整个进程杀掉”就完事。得精确到线程。

$ top -Hbp 28342 -n1 | head -10

  PID USER      %CPU COMMAND
28342 app        0.3 java
28355 app       85.1 java          ← 这个线程!
28356 app        0.1 java
28357 app        2.3 java

-H 开启线程视图,-p 指定进程。结果很清楚:TID 28355 的线程占了 85.1% CPU。把它转成十六进制(jstack 用):

$ printf '%x
' 28355
6ec3

然后导出线程 dump:

$ jstack 28342 > /tmp/jstack_dump.txt
$ grep -A 20 '0x6ec3' /tmp/jstack_dump.txt
"http-nio-8080-exec-37" #137 daemon prio=5 os_prio=0 tid=0x00007f8b4c001000 nid=0x6ec3 runnable
   java.lang.Thread.State: RUNNABLE
        at com.example.service.ReportService.generateReport(ReportService.java:156)
        at com.example.controller.ReportController.getReport(ReportController.java:42)
        ...

找到了。一个 HTTP 请求线程,卡在 ReportService.generateReport() 方法里出不来了。

如果进程不是 Java 的怎么办?Python 用 py-spy dump --pid PID;Go 用 curl localhost:6060/debug/pprof/goroutine?debug=2;C/C++ 直接用 perf(下一步)。

第四步:perf 揪出热点函数

jstack 只告诉你哪个方法,但方法内部到底哪行代码在烧 CPU?perf 上场。

$ perf top -p 28342

Samples: 189K of event 'cpu-clock', Event count (approx.): 45897250000
Overhead  Shared Object       Symbol
  48.21%  libc-2.31.so        [.] __strlen_avx2
  18.33%  perf-28342.map      [.] 0x00007f8b2c1a3f80
  12.05%  [kernel]            [k] copy_user_generic_string
   8.72%  libpthread-2.31.so  [.] __pthread_mutex_lock
   5.11%  libjvm.so           [.] SpinPause

看到了没有——strlen 占了 48% CPU。strlen 是字符串长度计算,正常情况下它不该出现在热点里。这意味着代码里可能在做一个超长字符串的反复操作:正则、拼接、序列化 JSON、或者大数据量的字符串替换。

用 perf record 采样 30 秒然后生成报告:

$ perf record -p 28342 -g -- sleep 30
$ perf report --stdio | head -30

# 调用栈一目了然:
48.21%  strlen
  └─ 38.15%  regexec          ← 正则引擎!
      └─ 35.22%  java.util.regex.Matcher.find
          └─ 33.08%  ReportService.generateReport
              └─ 30.12%  ReportService.formatCSVData

串联起来了。 generateReportformatCSVData → 用了正则去解析 CSV 大文件。几十万行 CSV 用正则一行行扫,CPU 不爆才怪。

第五步:生成火焰图——一张图看清一切

perf report 的输出是文本,不够直观。火焰图能把整个调用栈压缩成一张 SVG:

$ perf script > /tmp/out.perf
$ git clone https://github.com/brendangregg/FlameGraph.git
$ FlameGraph/stackcollapse-perf.pl /tmp/out.perf > /tmp/out.folded
$ FlameGraph/flamegraph.pl /tmp/out.folded > /tmp/flamegraph.svg

打开 SVG,你会看到一个宽得离谱的“平顶山”——strlen → regexec → Matcher.find → formatCSVData 这条调用链占了一半宽度。这就是你要优化的目标。

火焰图怎么看?宽 = 占 CPU 多,高 = 调用深。平顶(宽度大但高度小)= 函数本身耗时多。尖峰(宽度小但高度大)= 调用链长。优化先砍平顶。

Linux生产环境CPU 100%排查七步SOP流程图

第六步:strace 验证——系统调用视角

如果怀疑是系统调用层面出了问题(比如频繁 open/read 小文件),用 strace 统计:

$ strace -cp 28342 -o /tmp/strace_summary.txt
# 运行 15 秒后 Ctrl+C

$ cat /tmp/strace_summary.txt
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 45.21    5.231452         123     42531           read
 22.17    2.564021          89     28765           write
 18.33    2.121543         245      8654           futex
  8.09    0.936221         312      3001           poll
  3.15    0.364102          98      3712           brk
------ ----------- ----------- --------- --------- ----------------
100.00   11.571234                  90345       142 total

这里 read 占 45%,调用 42531 次——合理,正则扫 CSV 确实会产生大量读操作。但如果有异常信号(比如 brk 调用异常多 = 频繁内存分配),就要针对性优化。

第七步:根因修复与验证

回过头来修那个正则。原始代码大概是这样的:

// ❌ 用正则解析 CSV —— 灾难级实现
String pattern = "((?:\"|[^\"])*),((?:\"|[^\"])*)";
Pattern r = Pattern.compile(pattern);
for (String line : lines) {
    Matcher m = r.matcher(line);
    // 几十万行 × 正则回溯 = CPU 爆炸
}

修复方案:

// ✅ 用 Apache Commons CSV(流式解析,零正则)
Reader in = new FileReader("data.csv");
Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(in);
for (CSVRecord record : records) {
    String col1 = record.get(0);
    // 不触发回溯,O(n) 直读
}

或者更极简的——如果 CSV 格式简单(无内嵌逗号/引号),直接用 String.split(",")

// ✅ split 是 O(n),正则回溯是 O(2^n)
String[] cols = line.split(",", -1);

通用排查清单(遇到 CPU 高,按顺序问自己):

  1. ✅ 是 us(用户态)还是 sy(内核态)高?——确定查应用还是查内核
  2. ✅ 是单进程还是多进程一起飙?——确定是应用的锅还是系统的锅
  3. ✅ 线程级:哪个线程?——精确到线程 ID
  4. ✅ 热点函数:perf top 找到最耗 CPU 的函数
  5. ✅ 常见元凶:正则回溯、死循环、频繁 GC、N+1 循环、大对象反复序列化
  6. ✅ 验证:修复后用 ab -n 10000 -c 100 压测,确认 CPU 不再飙高

排查工具箱速查表

工具 用途 典型命令 适用场景
top 进程级 CPU/内存总览 top -bc -n1 第一步:快速定位进程
pidstat 线程级 CPU/内存/IO 统计 pidstat -t -p PID 1 持续监控单个线程
jstack Java 线程 dump jstack PID > dump.txt Java 进程专属
perf CPU 热点采样 + 火焰图 perf top -p PID 任何语言:找最耗CPU的函数
strace 系统调用追踪 strace -cp PID 怀疑系统调用层面问题
py-spy Python 进程采样 py-spy dump --pid PID Python 进程专属
htop 交互式进程监控 htop -p PID 需要频繁切换排序维度
vmstat 系统整体资源快照 vmstat 1 10 CPU+内存+IO 三维总览

四条血泪教训

1. 不要在生产环境反复试错。先用 perf record -p PID -g -- sleep 30 采 30 秒样,然后在本地分析 perf.data。生产环境跑 perf top 本身也有 2-3% CPU 开销。

2. 先止血,再排查。如果接口已经全部超时,别在那里慢慢看 jstack——先重启、先切流量、先限流。可用性 > 排查完整性

3. 正则回溯是 Java CPU 飙高的头号杀手。我遇到过四次线上 CPU 100%,三次是正则。养成习惯:永远不用正则解析结构化数据(CSV/JSON/XML),永远用专用解析器。

4. 保留现场。出问题后别急着重启——先 jstack、先 perf record、先 top -b -n1 > /tmp/top_snapshot.txt没有快照的故障复盘等于猜谜。

常见问题 FAQ

Q: CPU 100% 但 top 里 wa(I/O wait)也很高,先查哪个?

先查 I/O。wa 高意味着 CPU 在等磁盘/网络,此时 us 高可能是“虚假的高”——进程在忙等 I/O 完成而非真的在计算。用 iostat -x 1iotop 定位是哪个磁盘/哪个进程在产生 I/O。如果确认 I/O 没问题后再按本文流程查 CPU。

Q: jstack 导出 thread dump 时 Java 进程会卡住吗?

会短暂暂停所有线程(SafePoint),通常几十毫秒到几百毫秒。对于大部分应用可以忽略不计。但如果你的应用对延迟极度敏感(如高频交易),建议用 jcmd PID Thread.print 代替 jstack,或者用异步采样工具如 async-profiler。

Q: perf 提示 “Permission denied” / “Cannot access memory” 怎么办?

三个可能的原因:(1) /proc/sys/kernel/perf_event_paranoid 值太高——设为 -1;(2) 当前用户不是 root 且没有 CAP_SYS_ADMIN——用 root 执行;(3) Java 进程没开 -XX:+PreserveFramePointer——加上这个 JVM 参数重启后 perf 才能正确展开调用栈。

Q: 排查完了,怎么防止下次再出现?

三个防线:(1) 监控告警——CPU > 80% 持续 2 分钟就发告警,不等 100%;(2) 限流熔断——接口加 Rate Limiter,单个请求超 30 秒自动熔断;(3) 上线前压测——任何涉及正则/大循环的代码,上线前用 abwrk 跑一轮基准压测,对比 CPU 曲线。

了解更多

如果你对性能排查感兴趣,本站还有这些相关文章:

免责声明:本文内容基于个人生产环境实战经验编写,所有命令和工具用法均已在 Ubuntu 20.04/22.04/24.04 及 CentOS 7/Stream 上验证。不同环境可能有差异,生产环境操作前建议先在测试环境验证。文中的排查方法不构成任何商业服务承诺。

📎 相关阅读:Python并发编程选型指南:ThreadPoolExecutor vs asyncio vs ProcessPoolExecutor实测对比(2026) — 同一台机器实测四种并发方案,附选型决策树。

🔗 推荐阅读:Linux性能剖析实战:perf工具从CPU采样到火焰图生成全流程 — 深入CPU热点定位的下一步。

📌 排查系列新文:Python 生产环境内存泄漏排查实战:tracemalloc + objgraph + gc 三件套从发现到修复

📖 相关推荐:Redis生产环境踩坑实录:缓存穿透、雪崩、热点Key——从凌晨告警到根治

📖 扩展阅读:eBPF + bpftrace 生产环境调试实战 — 不用改代码不用重启,一行命令定位线上问题。

🔗 延伸阅读:当 CPU 问题排查完后还遇到进程假死无日志的情况,试试 strace 生产环境调试完全指南,不重启不改代码就能看到程序在系统调用层面到底卡在哪里。

📡 延伸阅读:Linux 网络故障排查实战:从 TCP 超时到连接池耗尽的全链路诊断

📤 分享这篇文章