云服务器 CPU 突然打满 100%?用 top、pidstat 和 perf 往下追
监控突然报 CPU 100%,或登录后 top 里 idle 几乎为 0,第一步不是立刻杀进程或重启,而是先分清:这 100% 到底是用户态在算、内核在忙,还是在等 I/O,甚至是云主机被宿主机抢走的时间。下面按 top → pidstat → perf 的顺序往下追,命令在 Ubuntu 22.04/24.04 和 openEuler 上都适用。
一、先确认“打满”的是哪一类时间
打开 top,看最上方的 Cpu(s) 一行(按 1 可展开到每颗逻辑 CPU):
- us:用户态。常见是业务进程、脚本、编译、压测在计算。
- sy:内核态。频繁系统调用、密集读写小文件、大量网络中断处理时会升高。
- wa:等待块设备 I/O。这时 CPU 其实在空等磁盘,不是算术意义上的“算满”。
- st:被宿主机抢走的时间(steal)。云服务器上这一项特别值得看:虚拟机觉得自己很忙,实际可能是物理机在争抢。
- id:空闲。多核机器上,某一核 100% 而整机平均只有 25%,和“整机打满”不是一回事。
也可以先看负载,并用 mpstat 按核观察(同样来自 sysstat):
uptime
mpstat -P ALL 1 5
load average 高只说明可运行或不可中断睡眠的任务多,不一定等于 CPU 算满。wa 很高、us/sy 并不高时,应该去查磁盘和 I/O,而不是先用 perf 看热点函数。st 持续偏高时,先看规格是否过小、同一时段是否有争抢,再决定是否调整规格,而不是在虚拟机里盲目优化业务代码。mpstat 还能看出是一颗核被打满、还是所有核一起忙,后者更像整机算力不够或有一批进程同时在跑。
二、用 top 定位到进程和线程
在 top 里按 P,按 CPU 占用排序。注意 %CPU 的分母通常是一颗逻辑核:4 核机器上某个进程显示 200%,表示它大约占了两颗核,并不代表“超出了物理极限”。
如果占用最高的是一个多线程进程(Java、nginx、数据库等),需要看线程:
top -H -p PID
或在 top 里先按 Shift+H 打开线程视图,再观察是所有线程一起忙,还是某几个线程把某一核打满。同时看进程用户、启动时间和命令行是否眼生。刚出现的高占用进程,可用下面命令看完整命令行和启动时间:
ps -o pid,lstart,etime,user,args -p PID
ls -l /proc/PID/cwd /proc/PID/exe
不要只盯排行第一的进程。有时是十几个进程各占若干百分比,加起来才把整机打满,这时应该按同一条命令行或同一个用户归类,而不是只分析某一个 PID。
有时“CPU 打满”只是 apt/dnf 在更新、备份脚本在压缩,或某个被忘记的压测还没停。先确认它该不该在跑,再决定要不要深入分析。
三、用 pidstat 看是持续打满还是一阵一阵
top 是瞬时画面,容易被短暂尖峰误导。pidstat(由 sysstat 包提供)适合按固定间隔采样:
Ubuntu/Debian
sudo apt install sysstat
openEuler / CentOS
sudo dnf install sysstat
每秒输出一次各进程的 CPU 占用,采 10 次
pidstat -u 1 10
关注 %usr、%system、%CPU,以及 CPU 列(跑在哪颗核上)。如果只想盯某一个进程及其线程:
pidstat -u -t -p PID 1 10
线程切换过于频繁时,CPU 不一定显示为“算得很重”,但 sy 和延迟会上去。可以顺带看上下文切换:
pidstat -w -p PID 1 10
cswch/s 是任务主动让出,nvcswch/s 是被强制抢走。后者长期很高,常见于线程明显多于可用核,或在热点锁上争抢。pidstat 能帮你判断:是某个线程持续占满一核(更像死循环或热点计算),还是许多线程轮流抢(更像锁竞争或异常重试)。
四、用 perf 看时间花在哪些函数上
已经知道 PID,但不知道它在干什么时,再用 perf。先安装与当前内核匹配的工具:
Ubuntu/Debian
sudo apt install linux-tools-common linux-tools-$(uname -r)
openEuler / CentOS
sudo dnf install perf
权限不够时,perf 会提示打不开性能事件。这和 /proc/sys/kernel/perf_event_paranoid 的当前值有关,不要为了一次排查就把生产机改成完全放开。能用当前权限采到用户态样本即可。采样本身有开销,生产上一次不要采太久。
看整机当前热点:
sudo perf top
或者对指定进程采样大约 10 秒,再离线看报告(-F 99 表示约每秒 99 次,避免和 100 Hz 的定时器完全同步):
sudo perf record -F 99 -p PID -g – sleep 10
sudo perf report --stdio
-g 会记录调用栈。报告里 Overhead 高的符号,就是这段时间里命中最多的位置。交互界面里按 + 可以展开调用链,按 Enter 可进入某个符号;用 --stdio 则适合把结果保存下来对照。下面是一份示意输出,不是某台机器的实测数据:
以下为示例
Overhead Command Shared Object Symbol
41.20% python libc.so.6 [.] _int_malloc
18.50% python app_module [.] process_item
6.10% python [kernel] [k] copy_user_enhanced_fast_string
解读时注意:
- 符号带 [k] 或来自 [kernel],说明不少时间在内核,要结合 sy、系统调用和 I/O 一起看。
- 用户态符号是 libc 里的 malloc、memcpy 等,可能是分配或拷贝过于频繁,而不一定是“算法写错了一行”。
- 没有调试符号时,常见只显示十六进制地址或 [unknown]。这时至少能区分是哪一个动态库;要看到函数名,需要对应的调试符号,或对业务进程保留符号表。
- JVM、部分解释型语言在默认符号下往往只能看到解释器或运行时,这并不代表 perf“没采到”,只是还需要该语言自己的剖析方式,不能只凭这一份报告下结论。
五、常见方向,但先别急着改
定位到进程和热点函数之后,再按方向处理,避免一上来就重启或 kill:
- 业务进程自己忙:看是否有失控循环、无节制重试、突然增大的请求量。结合该进程自己的日志和监控,比只看 CPU 百分比更有用。
- 内核态偏高:用 perf 确认是否大量陷入系统调用;若占用靠前的是 ksoftirqd、kworker 一类内核线程,再去看 /proc/interrupts 和当时的网络、块设备情况,而不是去调业务代码里的循环。
- st 偏高:问题可能在虚拟化层或规格,而不是进程写错了。
- 陌生进程占用很高:核对来源、启动时间和可执行文件路径,再决定是否停止。不要在没看清楚之前直接 kill -9。
查完后,如果改了应用配置或重启了某个服务,用同样的 top / pidstat 再观察一段时间,确认占用回落的是当初那个进程,而不是碰巧负载自己下去了。
六、小结
CPU 100% 先分清 us、sy、wa、st,再用 top 找到进程和线程,用 pidstat 确认是持续占用还是抖动,最后才用 perf 看热点函数。工具给出的是“时间花在哪里”,原因仍然要回到具体进程的行为上。采样保持短暂,解释报告时承认符号和运行时的限制,比急着给一个优化结论更可靠。
- 点赞
- 收藏
- 关注作者
评论(0)