3. CPU 瓶颈定位:用 top、htop、perf、mpstat 看穿 CPU
CPU 瓶颈,说白了就是 CPU 忙不过来,或者忙得没效率。很多新手一看到 CPU 使用率高就喊“瓶颈”,其实不一定。我见过太多案例,CPU 才用到 30%,系统却慢得像蜗牛——那是上下文切换在作怪。
这一章,咱们就盯死四个工具:top、htop、perf、mpstat。它们能帮你回答三个核心问题:
- CPU 到底有多忙?(使用率)
- 它在忙什么?(上下文切换、运行队列)
- 谁在捣乱?(进程/线程级定位)
3.1 top:第一眼印象,但别全信
top 是我上任何服务器第一个敲的命令。它给你一个快照:CPU 使用率、负载、进程列表。但说实话,top 的 CPU 信息太粗了。
你看到的是总的 CPU 使用率,分 user、system、nice、idle、iowait 等。我个人习惯先看 %idle——如果 idle 低于 20%,说明 CPU 确实忙。但忙什么?top 说不清楚。
top 输出关键行解读:
%Cpu(s): 45.2 us, 12.8 sy, 0.0 ni, 38.5 id, 3.0 wa, 0.3 hi, 0.2 si, 0.0 st
- us(user):用户态进程占用的 CPU 时间。高的话,说明应用层在干活。
- sy(system):内核态占用的 CPU 时间。超过 20% 就要警惕了,可能是系统调用太频繁。
- wa(iowait):等待 I/O 完成的时间。这个高,CPU 在等磁盘或网络,不是 CPU 本身慢。
- hi/si:硬中断和软中断。高的话,说明网络或硬件设备在疯狂打断 CPU。
我的习惯:top 只看第一眼,然后立刻按 1 键,看每个 CPU 核的使用情况。有时候总使用率不高,但某个核已经 100% 了——那是典型的“单线程瓶颈”。
3.2 htop:top 的增强版,更直观
htop 比 top 好看多了。彩色条显示每个 CPU 核的使用率,还能用鼠标操作。我个人更喜欢 htop 的进程树视图——按 F5,一眼看出父子进程关系。
htop 有个隐藏功能:按 t 键进入树形模式,按 H 隐藏用户线程。我在排查 Java 应用时经常用这个——Java 线程一多,top 里全是乱糟糟的线程名,htop 能帮你理清楚。
注意:htop 默认不安装。生产环境上可能没有。我建议你提前装好,或者至少知道怎么用 yum/apt 快速装。
# CentOS/RHEL
yum install -y htop
# Ubuntu/Debian
apt install -y htop
3.3 mpstat:多核 CPU 的“显微镜”
mpstat 是 sysstat 包里的工具。它能精确到每个 CPU 核,还能看上下文切换和中断频率。我个人觉得,mpstat 是定位 CPU 瓶颈的“第一利器”。
举个例子,我曾经遇到一个 Nginx 服务器,总 CPU 使用率才 30%,但请求延迟很高。用 mpstat 一看,发现 CPU 0 的软中断(%soft)高达 60%,其他核几乎空闲。原来是网卡中断都绑在 CPU 0 上了。
# 查看所有 CPU 核,每 2 秒刷新一次
mpstat -P ALL 2
# 输出示例:
# 02:45:12 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
# 02:45:14 PM all 35.20 0.00 12.80 3.00 0.00 0.30 0.00 0.00 0.00 48.70
# 02:45:14 PM 0 60.10 0.00 25.30 5.00 0.00 1.20 0.00 0.00 0.00 8.40
# 02:45:14 PM 1 10.30 0.00 3.20 1.00 0.00 0.10 0.00 0.00 0.00 85.40
关键指标:
- %soft:软中断占比。超过 5% 就要排查网络或驱动问题。
- %steal:虚拟机偷走的时间。云服务器上这个值高,说明宿主机超卖严重。
- %idle:空闲时间。低于 20% 是危险信号。
3.4 perf:CPU 的“火焰图”制造机
perf 是 Linux 性能分析的终极武器。它能采样 CPU 正在执行的指令,告诉你 CPU 的时间到底花在哪个函数上。
我记得有一次,一个 Redis 实例 CPU 使用率飙到 90%,但 QPS 没涨。用 perf top 一看,发现 lzf_decompress 占了大头——原来是客户端开启了压缩,但压缩率很低,CPU 全花在解压上了。
# 实时查看 CPU 热点函数
perf top
# 采样 10 秒,生成火焰图数据
perf record -a -g -- sleep 10
perf script > out.perf
# 然后用 FlameGraph 工具生成 SVG 火焰图
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph
./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl out.folded > flame.svg
我的建议:perf 的 -g 参数开启调用链采样,能看到函数调用路径。但采样频率别太高,默认就够。我曾经设成 1000Hz,结果 perf 自己占了不少 CPU,反而干扰了分析。
3.5 上下文切换:CPU 的“隐形杀手”
上下文切换,说白了就是 CPU 从一个进程切到另一个进程。每次切换都要保存和恢复寄存器、内存映射、缓存……开销不小。
怎么查?用 vmstat 1 看 cs(context switch)列,或者用 pidstat -w 1 看每个进程的切换次数。
我曾经踩过的坑:一个 Java 应用,开了 200 个线程,每个线程都在做很小的计算任务。上下文切换高达 30 万次/秒,CPU 的 system 时间占了 40%。后来改成线程池,线程数降到 16,性能反而提升了 3 倍。
| 指标 | 正常范围 | 警告阈值 | 危险阈值 |
|---|---|---|---|
| 上下文切换(次/秒) | < 1 万 | 1 万 ~ 5 万 | > 10 万 |
| 运行队列长度 | < CPU 核数 | CPU 核数 ~ 2 倍 | > 2 倍 CPU 核数 |
| CPU 系统态占比 | < 10% | 10% ~ 20% | > 30% |
3.6 运行队列:CPU 到底有多挤?
运行队列(run queue)是等待 CPU 的进程数。用 vmstat 1 看 r 列,或者用 sar -q 1 看 runq-sz。
运行队列超过 CPU 核数,说明 CPU 已经过载了。请求在排队,响应时间自然变长。我一般看 load average——如果 1 分钟负载超过 CPU 核数的 2 倍,基本可以断定 CPU 是瓶颈。
实战判断流程:
- 先看
top的 load average,如果 > CPU 核数 * 2,进入下一步。 - 用
mpstat -P ALL 1看每个核的使用率,找不均衡。 - 用
pidstat -w 1看上下文切换高的进程。 - 用
perf top定位热点函数。 - 根据热点函数,优化代码或调整配置。