4、CPU问题定位:CPU飙高、上下文切换频繁、负载异常的排查方法
CPU出问题,说白了就是它「忙不过来」或者「忙得没效率」。我见过太多同学一看到CPU 100%就慌了,直接重启机器。其实大可不必。咱们一步步来,把问题拆开看。
4.1 先搞清楚:CPU飙高、上下文切换、负载异常,到底有啥区别?
这三个概念经常被混在一起说,但本质完全不同。我习惯用一个比喻来解释:
- CPU飙高:好比一个工人一直在干活,手没停过。可能是真忙,也可能是在做无用功。
- 上下文切换频繁:工人刚拿起扳手,又放下换螺丝刀,再放下换锤子。频繁换工具本身就很耗时间。
- 负载异常:工头给工人派了100个活,但工人只有两只手。活堆在那里,干不完。
你想想看,这三者经常同时出现,但根因可能完全不同。咱们得一个一个查。
4.2 CPU飙高:到底是谁在吃CPU?
CPU飙高是最常见的故障。我遇到过一个案例,线上服务突然从20%飙到95%,用户开始报超时。当时我第一反应不是重启,而是先看看谁在吃CPU。
4.2.1 用 top 命令快速定位
# 直接运行 top,按 P 键按CPU使用率排序
top -c
这里有个小技巧:-c 参数会显示完整的命令行,方便你看到是哪个进程的哪个启动参数。我曾经遇到过一个Java进程,名字叫 java,但其实是别人埋的挖矿程序。看完整命令行才露馅。
4.2.2 找到线程级别的消耗
光知道进程还不够,得知道是哪个线程在搞事情。尤其是Java、Go这类多线程应用。
# 找到进程PID后,查看它的线程
top -H -p <PID>
# 或者用 ps 命令
ps -mp <PID> -o THREAD,tid,time
嗯,这里要注意:top -H 显示的 TID 是十进制的。如果你要 dump 线程栈,很多工具需要十六进制。转换一下:printf "%x\n" <TID>。
4.2.3 火焰图:一眼看出热点
我个人非常推荐火焰图。它能把CPU时间花在哪个函数上一目了然。
# 使用 perf 采样
perf record -F 99 -p <PID> -g -- sleep 30
perf script > out.perf
# 生成火焰图(需要 FlameGraph 工具)
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > cpu.svg
火焰图怎么看?「平顶宽」的函数就是热点。如果看到 syscall 或者 lock 相关的调用占了大头,那问题可能不在业务逻辑,而在系统层面。
核心思路:CPU飙高排查三步走——进程级 → 线程级 → 函数级。别跳步,跳步容易漏信息。
4.3 上下文切换频繁:系统在「空转」
上下文切换本身不是坏事,但频繁切换意味着CPU大量时间花在了「换人」上,而不是真正干活。
4.3.1 怎么看上下文切换?
# 查看系统整体上下文切换
vmstat 1
# 重点关注 cs(context switch)列
# 如果 cs 持续超过 10万/秒,基本可以判定有问题
# 查看具体进程的上下文切换
pidstat -w 1
我记得有一次排查一个消息队列服务,CPU不高(30%左右),但吞吐量上不去。一看 vmstat,cs 列飙到 20万/秒。再查 pidstat -w,发现某个线程的 cswch/s(自愿上下文切换)特别高。
4.3.2 自愿切换 vs 非自愿切换
| 类型 | 含义 | 常见原因 |
|---|---|---|
| 自愿切换 (cswch) | 线程主动让出CPU,比如等待I/O、锁、sleep | 锁竞争激烈、I/O等待过多 |
| 非自愿切换 (nvcswch) | 时间片用完,被系统强制调度 | 线程数过多、CPU资源不足 |
怎么区分?pidstat -w -p <PID> 1 会分别显示 cswch/s 和 nvcswch/s。
避坑指南:我曾经遇到一个案例,非自愿切换很高,但CPU并不高。原因是开了几百个线程,每个线程只做一点点计算就休眠。系统调度器疲于奔命。解决方案很简单——用线程池限制线程数。
4.4 负载异常:CPU不忙,但活干不完
负载(Load Average)是个容易被误解的指标。很多人以为负载高就是CPU忙,其实不一定。
4.4.1 负载的本质
负载 = 正在运行的进程数 + 等待运行的进程数 + 等待I/O的进程数。
所以负载高可能有三种情况:
- CPU密集型:CPU使用率也高,好排查
- I/O密集型:CPU不高,但进程都在等磁盘或网络
- 锁竞争:进程在等锁释放,CPU可能也不高
4.4.2 区分CPU负载和I/O负载
# 看 CPU 使用率和负载的对比
# CPU 低,负载高 → 大概率是 I/O 问题
top
# 进一步确认 I/O 等待
iostat -x 1
# 看 %iowait 列,如果持续超过 10%,说明磁盘是瓶颈
我遇到过最典型的一个场景:数据库服务器CPU只有20%,但负载到了30。查了半天,发现是磁盘在做全表扫描,大量进程在等磁盘读数据。加了个索引,负载直接降到2。
4.4.3 用 uptime 看趋势
uptime
# 输出示例:load average: 2.34, 1.89, 1.56
三个数字分别对应1分钟、5分钟、15分钟的平均负载。如果1分钟远高于15分钟,说明问题是最近才出现的,可能是突增流量或代码刚上线。如果三个数字都高且平稳,说明系统已经持续过载很久了。
注意:负载的「高」是相对的。8核机器负载8不算高,2核机器负载8就快挂了。一定要结合CPU核数来看。一般建议负载不超过核数的70%。
4.5 综合排查:一个真实案例
最后分享一个我印象很深的案例。某天晚上,线上告警:CPU 95%,负载 40(32核机器)。
我的排查过程:
- top 看进程:发现是 Nginx 进程吃CPU。奇怪,Nginx 一般不会这么高。
- perf 采样:生成火焰图,发现热点在
ngx_http_upstream_check模块。 - 查配置:发现健康检查间隔设置成了 100ms,而且后端有200个上游。相当于每秒发 2000 个探测请求。
- 改配置:把健康检查间隔改成 1s,CPU 直接降到 15%。
你看,问题其实很简单。但如果不看火焰图,光靠猜,可能折腾一晚上都找不到根因。
4.6 总结:我的排查清单
遇到CPU问题,我一般按这个顺序来:
- 先看
top,确认是哪个进程 - 再看
vmstat,确认是CPU忙还是I/O忙 - 然后用
pidstat -w看上下文切换类型 - 最后用
perf或火焰图定位热点函数
说白了,排查CPU问题就像破案。别急着下结论,多收集证据。工具就这些,关键是你得知道什么时候用哪个。