问题背景
CPU 飙高是 Linux 服务器最常见的性能问题之一。典型表现为:监控告警触发(CPU 使用率超过 90%)、业务响应变慢、用户投诉接口超时、ssh 操作卡顿。很多运维同学遇到 CPU 飙高的第一反应是重启服务或直接 kill 进程,这种方法治标不治本,需要系统性地定位根因。
本文按照从全局到局部、从表象到根因的排查思路,分三阶段展开,覆盖从 60 秒快速诊断到代码级热点分析的完整链路。
一、核心知识点速览
排查 CPU 问题前需要理解几个关键概念:
load average:系统的"平均活跃进程数",包含正在运行和等待 I/O 的进程。如果 load > CPU 逻辑核数,说明有进程在排队。
%us / %sy / %wa / %id / %st:CPU 时间的分布——用户态、内核态、I/O 等待、空闲、被虚拟机管理程序偷走。
**上下文切换 (context switch)**:CPU 在不同进程/线程间切换的频率,过高会浪费 CPU 时间在调度上而非业务上。
**运行队列 (run queue)**:处于可运行状态但等待 CPU 调度的进程数,超过核数意味着 CPU 饱和。
不区分物理核和逻辑核的场景:排查时关注的是 CPU 的调度单位(逻辑核),nproc 或 lscpu 输出的 CPU(s) 就是逻辑核总数。
二、第一阶段:60 秒全局扫描
登录服务器后,不要急着翻日志,先用一组命令快速了解系统整体状态。这一阶段的目标是定性:到底是 CPU 瓶颈、内存瓶颈、IO 瓶颈,还是网络问题。
2.1 uptime —— 看负载基线
$ uptime 1415 up 12 days, 3:21, 2 users, load average: 18.52, 15.34, 10.87
如果 load average 超过 CPU 逻辑核数,说明有任务在排队。单看 load 不够,需要结合后面的命令判断瓶颈在哪。
2.2 top —— 看 CPU 时间分布和 TOP 进程
$ top top - 1415 up 12 days, 3:21, 2 users, load average: 18.52, 15.34, 10.87 Tasks: 256 total, 3 running, 253 sleeping, 0 stopped, 0 zombie %Cpu(s): 12.5 us, 8.3 sy, 0.0 ni, 45.8 id, 33.3 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 31967.6 total, 1224.5 free, 18234.2 used, 12508.9 buff/cache MiB Swap: 4096.0 total, 345.2 free, 3750.8 used. 7123.4 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3456 root 20 0 ......
关键要看的是 %Cpu(s) 这一行的分布:
| 字段 | 含义 | 警戒线 |
|---|---|---|
| us | 用户态 CPU 时间 | 持续 > 70% 说明业务进程吃 CPU |
| sy | 内核态 CPU 时间 | 持续 > 30% 说明系统调用频繁或驱动问题 |
| wa | I/O 等待时间 | 持续 > 20% 说明磁盘是瓶颈 |
| id | 空闲时间 | 持续 < 10% 说明 CPU 饱和 |
| st | 被偷走的时间 | 云服务器上 > 5% 说明宿主机资源争抢 |
操作技巧:
按 P 键按 CPU 使用率排序
按 1 键展开每个逻辑核的负载分布
按 c 键显示完整命令行
2.3 vmstat —— 看调度和阻塞
$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 16 2 204800 12245 ...
只看前四列和 CPU 列:
| 字段 | 含义 | 警戒线 |
|---|---|---|
| r | 运行队列长度 | > 核数说明 CPU 饱和 |
| b | 不可中断睡眠进程数(D 状态) | > 0 说明 IO 或锁阻塞 |
| cs | 上下文切换次数/秒 | > 50000 说明线程切换频繁 |
| wa | I/O 等待 | > 20% 看 IO 子系统 |
关键判断:如果 r 高但 wa 低,CPU 是瓶颈;如果 b 高且 wa 高,磁盘是瓶颈;如果 cs 极高,说明线程过多或锁竞争严重。
2.4 mpstat —— 看各核负载是否均衡
$ mpstat -P ALL 1 3 Linux 5.15.0-91-generic ... 08/15/2025 _x86_64_ (32 CPU) 1415 CPU %usr %sys %iowait %idle 1416 all 12.50 8.30 33.30 45.80 1416 0 3.20 5.10 12.40 79.30 1416 1 60.50 15.30 5.20 19.00 ...
关注点:如果个别核 %usr 或 %sys 明显高于其他核,说明存在绑核或单线程瓶颈。如果所有核负载一致偏高,说明是全局限流(如 CPU 密集型任务、连接数打满)。
2.5 第一阶段判断矩阵
| 症状 | 可能原因 | 下一步 |
|---|---|---|
| us 高 + r 高 + wa 低 | CPU 密集型任务 | 第二阶段定位进程 |
| sy 高 + cs 高 | 锁竞争 / 系统调用频繁 | strace/perf 深挖 |
| wa 高 + b 高 | 磁盘 IO 瓶颈 | iostat/iotop 定位 |
| st > 5%(云服务器) | 宿主机超分 | 迁移实例或提工单 |
| 单核 us 高 + 其他低 | 单线程瓶颈 | 确认是否有绑核 |
三、第二阶段:定位进程与线程
全局扫描定位了方向后,接下来锁定嫌疑进程。
3.1 pidstat —— 按进程统计 CPU
# 每 1 秒统计一次,取 CPU 使用率最高的前 15 个 $ pidstat -u 1 5 | sort -k 8 -nr | head -15 Linux 5.15.0-91-generic ... 08/15/2025 _x86_64_ (32 CPU) 1422 UID PID %usr %system %guest %wait %CPU CPU Command 1423 0 3456 85.30 2.10 0.00 0.00 87.40 2 java 1423 999 5678 12.40 1.20 0.00 0.00 13.60 15 nginx
%wait 列(需要 procps-ng 4.0+)表示进程处于可运行状态但等待 CPU 调度的百分比。如果 %wait 高说明 CPU 确实是瓶颈,进程在排队等待调度。
3.2 top -H —— 看线程级 CPU 消耗
找到嫌疑进程后,看是进程内的哪个线程在吃 CPU:
$ top -H -p 3456
或者一步到位:
$ ps -p 3456 -To pid,tid,%cpu,cmd --sort=-%cpu | head -10 PID TID %CPU CMD 3456 3456 0.0 java 3456 6789 42.5 java 3456 6790 38.1 java
记下高 CPU 的 TID,转成十六进制用于线程 dump:
$ printf "%x " 6789 1a85
这个十六进制值在线程 Dump(jstack、pstack、/proc/
3.3 perf top —— 实时代码级热点
perf top 能直接显示哪个函数在消耗 CPU,比 top 精确到代码级别:
# 实时查看 CPU 热点函数(建议先装 linux-tools-common) $ sudo perf top -p 3456 -g
输出示例:
Samples: 1M of event 'cpu-clock', 4000 Hz, Event count (approx.): ... Overhead Shared Object Symbol 35.20% java [.] JFFS2_write_super 12.50% libjvm.so [.] Unsafe_GetLongVolatile 8.30% [kernel] [k] _raw_spin_unlock_irqrestore
-g 参数显示调用链,可以逐层回溯是谁调用了这个热点函数。
3.4 perf record:生成火焰图
当需要更精确的分析或对外汇报时,用采样模式:
# 采样 30 秒 $ sudo perf record -F 99 -p 3456 -g --sleep 30 $ sudo perf report --no-children
参数说明:
-F 99:每秒采样 99 次,兼顾精度和开销
-g:记录调用链
-k 1:强制包含内核栈(重要,否则大量 [unknown])
--no-children:展开真实调用深度,避免折叠优化隐藏信息
生成火焰图:
$ sudo perf script -i perf.data > out.perf $ git clone https://github.com/brendangregg/FlameGraph $ cd FlameGraph $ ./stackcollapse-perf.pl ../out.perf > out.folded $ ./flamegraph.pl out.folded > cpu.svg
火焰图横轴是采样比例,纵轴是调用栈。找到最宽的"平顶"区域就是热点函数,从那里往上看调用路径。
3.5 strace -c —— 统计系统调用开销
如果 %sy(内核态 CPU)偏高,用 strace 看系统调用耗时分布:
# 统计 5 秒内系统调用的耗时分布(不带 -p 则启动新进程) $ sudo strace -c -p 3456 strace: Process 3456 attached ^C % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 45.20 0.452000 12 376 futex 30.15 0.301500 5 603 epoll_wait 12.10 0.121000 4 302 write 8.50 0.085000 3 283 read
常见高耗时系统调用:
| 系统调用 | 含义 | 可能问题 |
|---|---|---|
| futex | 用户态锁操作 | 锁竞争激烈,线程同步瓶颈 |
| epoll_wait | 等待事件 | I/O 多路复用,正常等待 |
| write/read | 文件读写 | IO 频繁,检查是否写日志 |
| sendto/recvfrom | 网络收发 | 网络 IO 密集 |
四、第三阶段:根因分类与深挖
根据前两阶段的数据,将 CPU 飙高归入以下典型类别进行针对性排查。
4.1 CPU 密集型任务
特征:%usr 高(> 70%),perf top 显示业务函数占主导。
排查方向:
查看是否有新上线的代码逻辑变更
检查定时任务(crontab)是否集中在同一时段
使用 perf record 生成火焰图确认业务热点
典型场景:突发流量导致连接处理数暴增、某些查询 SQL 通过 ORM 映射成循环查询、正则表达式回溯导致 CPU 打满。
4.2 死循环或无限递归
特征:单进程 CPU 固定 100%(单核),strace -c 显示某个系统调用频率极高。
排查方向:
# 先确认是单线程还是多线程在跑 $ top -H -p# 对 Java 应用取 Thread Dump $ kill -3 # 结合前面十六进制的 TID 定位线程
典型场景:某次 while 循环缺少 break 条件、递归调用缺少终止条件、异常重试机制进入死循环。
4.3 锁竞争激烈
特征:%sy 高(> 30%),vmstat 1 的 cs 列 > 50000,strace -e futex 高频。
# 只看 futex 相关调用 $ sudo strace -e futex -p# 或用 perf 分析锁相关事件 $ sudo perf record -e sched:sched_stat_runtime -p -- sleep 10 $ sudo perf report
典型场景:高并发下 HashMap 扩容死循环(JDK 1.7 及以前)、ArrayList 并发写入、热门行数据库锁、Redis 热点 key。
4.4 上下文切换过高
特征:CPU 消耗不高(%idle 高),但 load average 高,vmstat 的 cs > 80000。
# 查看自愿/非自愿上下文切换 $ pidstat -w 1 5
**自愿切换 (cswch/s)**:线程等待 I/O 或锁时主动让出 CPU,过高可能因为 IO 频繁。
**非自愿切换 (nvcswch/s)**:时间片用完被强制调度,过高因为线程数过多(超过 CPU 核数太多)。
4.5 中断处理过多
特征:%si(软中断)或 %hi(硬中断)异常高。
# 查看中断分布 $ cat /proc/interrupts # 查看软中断 $ cat /proc/softirqs
典型场景:网卡 RX 队列中断集中到单个 CPU(RPS/RSS 未配置)、TPS 过高导致网络软中断多。
4.6 云服务器 vCPU 争抢
特征:%st(steal)持续 > 5%,且 %idle 低。
# 持续观察 steal 值
$ mpstat -P ALL 1 | grep -v Linux | awk '{print $1,$13}'
%st 表示 hypervisor 从当前 VM 偷走的 CPU 时间。如果持续高于 10%,说明宿主机的 CPU 已超分严重,直接提工单找云厂商。
五、CPU 飙高 8 大根因速查表
| # | 根因 | 关键症状 | 确诊命令 | 应对措施 |
|---|---|---|---|---|
| 1 | 业务代码 CPU 密集 | %usr > 70%,load > 核数 | perf report | 优化算法/增加缓存/扩容 |
| 2 | 死循环/无限递归 | 单进程 CPU 100%,无 IO | strace -c 、jstack | 修复代码逻辑 |
| 3 | 锁竞争 | %sy > 30%,futex 高频 | strace -e futex | 减少锁粒度/无锁化 |
| 4 | 上下文切换过多 | %idle 高但 load 高,cs > 50000 | vmstat 1 | 减少线程数/使用协程 |
| 5 | I/O 密集导致 CPU 等待 | wa > 20%,b 列高 | iostat -x 1 | 换 SSD/优化 SQL/加缓存 |
| 6 | 内核内存回收 | %sys 高,kswapd0 进程 CPU 高 | vmstat 1 | 增加内存/检查内存泄漏 |
| 7 | 软中断/网络收包 | %si 高,单核中断不均衡 | cat /proc/softirqs | RPS/RSS 设置/网卡多队列 |
| 8 | 云服务器 steal 高 | %st > 10% | mpstat -P ALL 1 | 提工单/换实例/非高峰期操作 |
六、生产环境注意事项
采样频率控制:perf record 的 -F 不要超过 10000Hz,否则 perf 本身的开销会成为干扰。生产环境推荐 99-997Hz。
必须包含内核栈:perf record -k 1,否则大量热点显示为 [unknown]。
谨慎使用 strace -p:在高并发进程上附加 strace 会导致进程显著变慢(5~10 倍性能下降),建议只在低峰期使用或先统计后采样。
不要直接 kill 高 CPU 进程:除非确认是恶意进程,否则先确认进程归属(java、mysql、httpd 可能有业务在跑)。
绑核排查:如果看到单核 100% 而其他核空闲,检查是否有 taskset 绑核或 irqbalance 未运行。
云服务器注意 %st:如果 %st 高,本地优化无效,直接联系云厂商。
操作前记录现场:
$ date +%F_%H:%M:%S > /tmp/cpu_debug_$(date +%s).txt $ uptime >> /tmp/cpu_debug_*.txt $ top -bn1 >> /tmp/cpu_debug_*.txt $ vmstat 1 5 >> /tmp/cpu_debug_*.txt
七、总结
CPU 飙高排查的核心思路可以浓缩为三句话:
先 60 秒全局扫描——用 uptime / top / vmstat / mpstat 定性是 CPU、IO、内存还是网络问题。
再锁定嫌疑进程——用 pidstat / perf top / strace 热点分析定位到具体的进程、线程、函数。
最后对症下药——根据根因分类走对应的优化路径:算法优化、锁优化、扩容、配置调整。
盲目重启或加机器只是暂时掩盖问题,只有找到根因才能彻底解决。建议将上述排查流程固化成一个排查脚本,每次 CPU 飙高时自动采集现场数据,再按流程逐步分析。
全部0条评论
快来发表一下你的评论吧 !