Skip to content

title: Linux 监控 CPU:top 一行 5 个百分比字段排障决策树 description: Linux CPU 监控深度解析:top 一行 %usr/%sys/%iowait/%steal/%irq 五个字段的排障决策树,mpstat/pidstat/vmstat/sar 工具链组合使用,4 个真实排障案例,含 NUMA 绑核和 CFS 配额两个隐性 CPU 问题。 tags: [linux, cpu, monitoring, mpstat, pidstat, sysstat] lang: zh date: 2026-10-23 answer: | Linux CPU 排障从 top 一行 5 个字段开始:%usr 高 → 代码优化或扩容;%sys 高 → 系统调用/审计/内核;%iowait 高 → 磁盘瓶颈不是 CPU;%steal 高 → 虚拟化层被抢;%irq/%soft 高 → 网络/中断。 然后按 top → mpstat 看单核 → pidstat 看进程 → iostat 看磁盘 → perf 看热点函数 五层递进。 隐性坑:容器 CFS 配额导致 %idle 高但业务卡;NUMA 绑核导致跨节点访存慢。 faq: - q: %iowait 高就一定说明 CPU 忙吗? a: 不是。%iowait 是"CPU 空闲但等待磁盘 I/O 完成"的时间百分比。真正的问题是磁盘,不是 CPU。高 %iowait 结合 iostat 的 %util 和 await 看磁盘,扩容 CPU 没用。 - q: top 里 %Cpu(s) 平均值 40%,为什么接口还是慢? a: 平均值会掩盖单核热点。用 mpstat -P ALL 看每个核心,如果有某个核 100% 而其他核空闲,可能是线程绑核(NUMA)或 Java/GC 单线程问题。另外看 CFS 容器配额:CFS quota 限流会让业务线程在 CPU 空闲时被强行暂停。 - q: %steal 高说明什么? a: 虚拟化场景下"被 hypervisor 抢走的时间"。云主机邻居吵闹(noisy neighbor)或宿主机超卖会拉高这个值。物理机不该有 %steal。这个字段是判断"云主机是不是共享超卖"最直接的信号。 - q: top 的 %CPU 和 pidstat 的 %CPU 是同一个意思吗? a: 数值算法接近但含义不同。top 的 %CPU 是进程占用整机 CPU 的百分比(多核场景下单进程最多 100%×N 核);pidstat 默认也是这个口径,但加 -I 参数会除以 CPU 总数,返回 0-100 范围的归一化百分比。跨机器对比要统一口径。


Linux 监控 CPU:top 一行 5 个百分比字段排障决策树

CPU 满了,是最容易被误诊的性能问题。90% 的人看到 %Cpu(s): 100% us 就冲去重启进程,看到 %iowait 高 就换块更快的 SSD,看到 %sys 高 就以为是系统调用太多要优化代码——结果方向完全错了。

写这篇的时候,我复盘了自己踩过的三个坑: - 生产环境 Java 应用 CPU 100%,重启了 5 次,最后是 GC 单线程导致的 - 一台机器 %iowait 40% 但业务慢,扩容到 16 核还是慢——磁盘是瓶颈不是 CPU - K8s 集群里业务线程被 CFS quota 限流,top 显示 %idle 95%,业务却在超时无响应

入口:top 一行的 5 个字段

CPU 排障的第一站永远是 top。不用学什么工具,先看第一屏顶部的这一行:

top -bn1 | head -5

你应该看到什么:

%Cpu(s):  12.3 us,  2.1 sy,  0.0 ni,  84.5 id,  0.5 wa,  0.0 hi,  0.4 si,  0.0 st

这一行有 5 个关键字段,每个字段高都是完全不同的问题:

字段 含义 高了说明什么
%us / %usr 用户态 CPU 时间 应用代码在烧 CPU
%sy / %system 内核态 CPU 时间 系统调用多 / 内核驱动忙 / 审计钩子
%wa / %iowait 等待 I/O 完成 磁盘瓶颈,不是 CPU
%hi 硬件中断 网卡/磁盘中断处理
%si 软件中断 网络协议栈处理
%st 被 hypervisor 偷走 云主机超卖/邻居吵闹
%ni nice 进程 人为降优先级,通常可忽略
%idle 空闲 越接近 100% 越健康

注意一个反直觉的事实:%iowait 高不代表 CPU 忙。它是"CPU 空闲但用户在等磁盘"的时间百分比。看到 %iowait 40% 该去查磁盘,别去扩 CPU。

排障决策树

拿到 top 第一行,按下面这张决策树走:

top 显示 CPU 使用率高
├─ %usr 高(>70%)
│  ├─ 单进程占大头?→ pidstat -u 定位进程 → strace/perf 看热点
│  └─ 多个进程都有?→ 应用架构问题,考虑水平扩容
├─ %sys 高(>30%)
│  ├─ 网络中断多?→ mpstat 看 %irq/%soft
│  ├─ 系统调用多?→ strace -c -p <pid> 看调用频次
│  └─ 审计/安全软件?→ auditctl -l / 卸载安全 agent
├─ %iowait 高(>20%)
│  ├─ %util 高?→ 磁盘打满,换更快的盘或优化 IO
│  ├─ await 高但 %util 不高?→ 网络存储/挂载问题
│  └─ 频繁 fsync?→ 应用层写策略问题(WAL/事务日志)
├─ %irq + %soft 高(>15%)
│  ├─ 网卡中断?→ ethtool -n <eth0> 看队列均衡
│  └─ 中断绑核?→ irqbalance 或手动绑多核
└─ %steal 高(>5%)
   └─ 云主机被邻居影响 → 换实例规格或联系云厂商

场景一:%usr 高 → 找进程

%usr 高是"应用代码在吃 CPU"。第一步是定位是哪个进程、哪个线程。

# 1. 看 top 里按 CPU 排序
top -bn1 | sort -k 9 -n -r | head -10

# 2. 用 pidstat 精确定位(sysstat 包)
pidstat -u 1 5    # 每 1 秒刷新,共 5 次

# 3. 如果怀疑是线程问题(Java/Go 常见)
pidstat -u -t 1 3    # -t 展开到线程级

你应该看到什么: - pidstat 输出里 %CPU 列最高的进程 - 加 -t 后能看到 %CPU 列的线程(TID),一个进程可能有单线程 100% 而其他线程 0%

真实案例 1:Java 应用 CPU 100% 重启无效

一台 Java 应用服务器,top 显示 100% us,重启无效。用 pidstat -u -t 1 3 看到只有一个 TID(GC Thread #2)持续 100%,其他线程 0%。

排查路径:top -H -p <pid> 看线程名 → 定位到 GC 线程 → 打开 GC 日志发现 Full GC 频繁 → 根因是堆内存 OOM 前兆。

修复:加 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError,扩大 Xmx,优化代码对象持有。

教训:进程级 %usr 是平均,线程级才是真相。Java 尤其如此——单线程 GC 就能吃满整个核。

场景二:%sys 高 → 找系统调用和内核开销

%sys 高不是"代码写不好",通常是三种情况:网络中断风暴、系统调用频繁、审计/安全钩子。

# 1. 看是硬中断还是软中断
mpstat 1 5     # 看 %irq 和 %soft 分布

# 2. 如果 %sys 高但 %irq/%soft 不高,抓系统调用
strace -c -p <pid> -f   # -c 汇总,-f 跟随 fork
# 按回车停止,看耗时最多的 syscall

# 3. 看是哪些 syscall 在烧
perf top -p <pid>       # 看内核函数热点

你应该看到什么: - mpstat 里 %irq 或 %soft 明显高于其他行 → 中断是主因 - strace -c 输出里某个 syscall(比如 futex、select、epoll_wait)耗时最多

真实案例 2:%sys 40% 是安全 agent 的锅

一台服务器 %sys 长期 40%,业务方抱怨启动慢。排查发现: - strace -c -p <业务pid> 里 futex 占了 80% - auditctl -l 里审计规则有 200 多条,包括"监控所有 /var/log/*"、"监控所有网络连接" - 卸载安全 agent 的 audit 规则后,%sys 降到 5%

教训:%sys 高优先查审计和安全软件,而不是应用代码。安全 agent 是"看不见的 CPU 杀手"。

场景三:%iowait 高 → 找磁盘

反直觉:%iowait 高是磁盘问题,不是 CPU 问题。但 90% 的人第一反应是扩容 CPU。

# 1. 用 iostat 看每个磁盘的状态
iostat -xz 1 5

# 2. 关注三个字段
#    %util  = 磁盘忙碌率(>90% 说明磁盘打满)
#    await  = 平均 IO 等待时间(毫秒)
#    avgqu-sz = 平均队列长度

你应该看到什么: - %util 接近 100% → 磁盘被打满,扩容 CPU 没用 - await > 100ms → 单次 IO 慢,可能是磁盘老化或网络存储抖动 - avgqu-sz > 8 → IO 排队严重

真实案例 3:%iowait 40% 扩容 16 核依然慢

一台 MySQL 服务器 %iowait 40%,业务方要求扩容。扩容到 16 核,依然慢。用 iostat -xz 看到: - sda %util = 98%(磁盘打满) - sda await = 250ms(单次 IO 250 毫秒) - sda avgqu-sz = 42(IO 队列长度 42)

根因是 MySQL innodb_log_file_size 太小 + WAL 频繁刷盘,撑爆单块 SSD。

修复: - 拆双 SSD,把 innodb_log 分到独立盘 - 或改 innodb_flush_log_at_trx_commit=2(可容忍少量数据丢失)

教训:%iowait 高先看 iostat %util。磁盘是瓶颈时,扩容 CPU 是浪费钱。

场景四:%irq/%soft 高 → 找网络中断

网络密集型机器(网关、代理、K8s 节点)经常出现 %soft 高。

# 1. 看具体是哪些中断
cat /proc/interrupts | head -20

# 2. 看中断在各核的分布
watch -n 1 "cat /proc/interrupts | grep eth0"

# 3. 看网卡多队列是否生效
ethtool -l eth0     # 看 Number of combined RX queues

你应该看到什么: - /proc/interrupts 里某个 IRQ 编号(比如 123:)在某一个 CPU 上计数持续增长 - ethtool -l 里 "Combined" 显示 1 而不是 CPU 数 → 网卡没开多队列

修复: - 打开网卡多队列:ethtool -L eth0 combined 8 - 或让 irqbalance 自动平衡:systemctl status irqbalance

场景五:%steal 高 → 找虚拟化层

这个字段是判断"云主机是不是超卖"最直接的信号。

# 看 %steal 长期分布
sar -u -P ALL 60 30     # 每 60 秒采一次,共 30 次

你应该看到什么: - 物理机上 %steal 应该恒为 0 - 云主机上偶发 %steal 2-5% 是正常的 - 长期 %steal > 10% → 邻居吵闹或宿主机超卖

修复路径: 1. 换独享实例规格(阿里云独享 vs 共享) 2. 或者接受性能抖动,把业务容忍度调高

真实案例 4:一台云主机 CPU 慢但 %idle 95%

业务方反馈"云主机突然变慢",但 top 显示 %idle 95%。用 mpstat -P ALL 1 5 发现: - %steal 长期 15-20% - %idle 85-90%(看似空闲)

这是典型的云主机邻居吵闹:宿主机其他虚拟机把 CPU 偷走了,你的实例被限流。

修复:联系云厂商升级到独享实例,或换实例。

隐性坑 1:容器 CFS quota 限流

这是 K8s 集群最容易踩的坑。top 显示 %idle 90%,但业务超时无响应。

# 1. 看容器是否被 CFS 限流
cat /sys/fs/cgroup/cpu/cpu.stat     # cgroup v1
# 或 cgroup v2
cat /sys/fs/cgroup/cpu.max

# 2. 看是否被 throttle
grep throttled /sys/fs/cgroup/cpu.stat
# nr_throttled: 累计被限流次数
# throttled_time: 累计被限流时间(纳秒)

你应该看到什么: - nr_throttled 持续增加 → 业务被 CFS quota 限流 - throttled_time 每秒增量 > 10ms → 业务明显受影响

修复: - 调大 cpu.cfs_quota_us(比如从 50000 提到 200000) - 或去掉 CPU limit,只留 requests

教训:K8s 里看 CPU 一定要看 cgroup 的 throttle 数据,top 里的 %idle 会骗人。

隐性坑 2:NUMA 绑核导致跨节点访存

大内存多核机器(64 核 + 256G 内存)的隐性性能杀手。

# 1. 看进程绑在哪个 NUMA 节点
taskset -pc <pid>

# 2. 看进程的 NUMA 分布
numastat -p <pid>

# 3. 看 NUMA 节点间迁移次数
numastat --all | grep migrate

你应该看到什么: - numastat -p 里 numa_hit 和 numa_miss 差距大 → 跨节点访存多 - numastat --all 里 numamigrate 计数持续增长 → 线程在节点间乱跑

修复:用 taskset 或 numactl 把进程绑到单一 NUMA 节点:

numactl --cpunodebind=0 --membind=0 java -jar app.jar

5 层递进工具链

CPU 排障的正确姿势是由粗到细 5 层递进:

层级 命令 用途
1 top 看整体 %us/%sy/%wa
2 mpstat -P ALL 看单核分布(找热点核)
3 pidstat -u -t 定位到具体进程和线程
4 iostat -xz / perf top 找磁盘或内核热点函数
5 perf record + perf report 生成火焰图,定位具体代码行

不要跳过中间层:直接 perf 一个 1000 进程的机器会输出爆炸。先用 top 定方向,再用 mpstat/pidstat 缩范围,最后 perf 定根因。

12 条速查命令

# 命令 用途
1 top -bn1 快速看一屏(非交互模式)
2 top -H -p <pid> 看进程内线程 CPU
3 mpstat -P ALL 1 5 每核分布
4 pidstat -u 1 5 进程 CPU
5 pidstat -u -t 1 3 线程 CPU(Java/Go 用)
6 iostat -xz 1 5 磁盘状态
7 sar -u 1 5 历史 CPU 采样
8 sar -u -f /var/log/sa/sa$(date +\%d) 查历史某一天的 CPU
9 perf top -p <pid> 进程热点函数
10 numastat -p <pid> NUMA 分布
11 cat /sys/fs/cgroup/cpu.stat 容器 CFS throttle
12 cat /proc/interrupts 中断分布

收尾:什么时候不是 CPU 问题

CPU 排障最容易犯的错误是把不是 CPU 的问题当 CPU 问题。以下场景看起来像 CPU 慢,其实不是:

  1. %iowait 高:磁盘问题,扩 CPU 没用
  2. %steal 高:虚拟化层问题,找云厂商
  3. top 显示 %idle 90% 但业务慢:可能是内存不足(换页)、GC、CFS 限流、NUMA 绑核错
  4. 单核 100% 其他核空闲:不是 CPU 容量问题,是线程调度问题

排障铁律:先分类(%us/%sy/%wa/%irq/%st)再定位(进程/线程/函数),不要一上来就重启。重启能解决的只是"重启能解决"的问题——生产环境里 90% 的性能问题重启没用。

这一点我也不完全确定:不同发行版(RHEL/Debian/Alpine)的 mpstat 版本对某些字段计算方式略有差异,同一份输出在不同系统里可能差 1-2 个百分点。跨机器对比时留 5% 的余量比较稳。