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。不用学什么工具,先看第一屏顶部的这一行:
你应该看到什么:
这一行有 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 应该恒为 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 节点:
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 慢,其实不是:
%iowait高:磁盘问题,扩 CPU 没用%steal高:虚拟化层问题,找云厂商- top 显示
%idle 90%但业务慢:可能是内存不足(换页)、GC、CFS 限流、NUMA 绑核错 - 单核 100% 其他核空闲:不是 CPU 容量问题,是线程调度问题
排障铁律:先分类(%us/%sy/%wa/%irq/%st)再定位(进程/线程/函数),不要一上来就重启。重启能解决的只是"重启能解决"的问题——生产环境里 90% 的性能问题重启没用。
这一点我也不完全确定:不同发行版(RHEL/Debian/Alpine)的 mpstat 版本对某些字段计算方式略有差异,同一份输出在不同系统里可能差 1-2 个百分点。跨机器对比时留 5% 的余量比较稳。