☰
Linux CPU使用率为什么总对不上?四个命令的底层逻辑差异
2026/10/22 15:56:25 网站建设 项目流程

1. 这不是“看个数字”那么简单:为什么四个命令测出的CPU使用率总对不上?

你有没有遇到过这样的场景:某天系统响应变慢,第一反应是敲top看一眼——显示 CPU 使用率才 35%,心里刚松口气,转头用sar -u 1 3跑三秒,平均值却报出 68%;再切到vmstat 1 3,r 列(可运行进程数)持续飙到 12,b 列(阻塞进程)也时不时跳到 2;最后跑个mpstat -P ALL 1 3,发现 CPU0 的 %usr 高达 92%,而 CPU7 却长期在 5% 以下“摸鱼”。你盯着终端发愣:到底哪个数才是“真实”的?系统到底忙不忙?

这根本不是命令写错了,也不是你手抖按错了键。vmstat、mpstat、top、sar四个工具,本质上在回答四个不同维度的问题,它们测量的从来就不是同一个“CPU使用率”。把它们混为一谈,就像用体温计去量血压、拿血压计去测血糖——数据都准,但问错了问题,答案再精确也是误导。我带过的几个刚转运维的同事,头三个月几乎都在这个坑里反复打转:top说没事,sar说快爆了,mpstat说某核已焊死,vmstat却在疯狂刷“bi”(块设备中断)和“in”(每秒中断数)。最后排查下来,是某数据库的 WAL 日志刷盘策略不当,导致 I/O 等待堆积,大量进程卡在不可中断睡眠(D 状态),top的默认视图只统计 R/S 状态进程的 CPU 时间,自然严重低估了真实负载;而sar -u的采样逻辑会把 D 状态进程的等待时间折算进%iowait,vmstat的wa列则直接暴露了 I/O 等待占比。这四个数字,其实是同一台机器在不同“镜头”下的四张快照:top是广角动态直播,sar是带时间戳的精密仪表盘,mpstat是给每个 CPU 核心单独拍的特写,vmstat则是俯瞰整个系统资源流转的全局沙盘。

所以,这篇内容的核心,不是教你“怎么敲命令”,而是帮你建立一套系统级 CPU 负载的诊断思维框架。它适合三类人:一是刚接触 Linux 性能分析的新手,需要避开“看到数字就慌”的本能反应;二是有经验但常被多命令结果矛盾困扰的中级工程师,需要厘清工具底层逻辑;三是负责 SLO(服务等级目标)保障的架构师,需要理解监控指标背后的物理意义,避免用top的瞬时值去定义一个服务的“CPU 使用率 SLA”。接下来,我会一层层拆开这四个命令的“心脏”,告诉你它们各自在数什么、怎么数、为什么这么数,以及当它们打架时,你该信谁、先查什么、最后怎么闭环。

2. 工具设计哲学大拆解:四个命令的“世界观”差异

2.1top:实时动态的“进程视角”——它关心的是“谁在用”

top的本质,是一个基于 /proc 文件系统的进程级实时监控器。它不直接读取 CPU 硬件寄存器,而是周期性地扫描/proc/[pid]/stat文件,从中提取每个进程的utime(用户态 CPU 时间)、stime(内核态 CPU 时间)等字段,再通过两次采样差值,计算出该进程在采样周期内的 CPU 占用百分比。它的核心假设是:系统负载 = 所有活跃进程消耗 CPU 时间的总和。

这个假设在绝大多数场景下成立,但它有三个关键“盲区”:

  • D 状态进程(不可中断睡眠)被完全忽略。当进程因等待磁盘 I/O 或某些内核锁而进入 D 状态时,它不再执行任何代码,utime/stime不再增长,top就认为它“没在用 CPU”。但现实是,这些进程正卡在硬件或内核路径上,其等待时间会直接拖慢整个系统的响应。top的%CPU列永远为 0,而vmstat的wa列却可能高达 40%。
  • 采样窗口极短且不稳定。top默认刷新间隔是 3 秒,但这个“3 秒”并非严格定时。它是在前一次刷新完成、屏幕重绘结束后才开始计时,如果某次刷新因大量进程信息渲染而耗时 800ms,那么实际采样间隔就变成了 3.8 秒。更麻烦的是,top的采样是“事件驱动”的——它只在刷新时刻抓取一次/proc快照,无法捕捉到发生在两次刷新之间的微突发(micro-burst)。一个持续 200ms、CPU 占用 100% 的尖峰,在top里可能完全消失,或者被平滑成一个 15% 的“平均值”。
  • CPU 核心粒度缺失。top默认展示的是所有 CPU 核心的加权平均值。它不会告诉你,是某个核心被单个 Java 应用的 GC 线程彻底霸占,还是所有核心都均匀分摊了 Web 请求。这种“平均主义”在负载均衡良好的系统里是优点,但在定位热点瓶颈时,就是致命的模糊。

提示:top的真正价值,不在于那个飘忽的%CPU数字,而在于它的交互式排序能力。按P(%CPU)、M(%MEM)、T(TIME+)排序,能瞬间定位出“最耗资源的罪魁祸首”。这才是它不可替代的地方。

2.2vmstat:系统资源流转的“全局沙盘”——它关心的是“发生了什么”

vmstat(virtual memory statistics)的名字已经泄露了它的野心:它要统计的,是整个虚拟内存子系统及其关联的 CPU、I/O、进程调度活动。它不深入单个进程,而是站在内核调度器和内存管理器的“上帝视角”,观察资源是如何被申请、分配、争抢和释放的。

vmstat的核心输出分为几大列:

  • r(runnable):当前处于可运行状态(R 状态)的进程数。这是衡量 CPU 是否过载的最原始、最无歧义的指标。如果r值长期大于 CPU 核心总数(例如 8 核机器r持续 > 8),说明有进程在排队等 CPU,系统已出现 CPU 争抢。
  • b(blocked):处于不可中断睡眠(D 状态)的进程数。这个数字一旦非零,就是 I/O 或内核锁瓶颈的明确信号。
  • swpd/free/buff/cache:内存使用全景图,揭示是否因内存不足触发了 swap,或 cache 是否被有效利用。
  • si/so:swap in/out 速率,直接反映内存压力。
  • bi/bo:块设备 I/O 速率(blocks per second),是磁盘吞吐的直接体现。
  • in:每秒中断次数(interrupts per second)。一个健康的服务器,in值通常在几百到几千之间;如果in突然飙升到数万,大概率是网卡软中断风暴或某个驱动异常。
  • cs:每秒上下文切换次数(context switches per second)。cs值过高(远超r + b的 10 倍)往往意味着进程/线程过于碎片化,或存在频繁的锁竞争。
  • us/sy/id/wa/st:这才是vmstat的 CPU 使用率。注意,这里的wa(I/O wait)是vmstat自己计算的,它等于(total time - time spent running user/kernel code - time spent idle) / total time。它不依赖于进程状态,而是从系统时间片的宏观角度反推出来的等待比例。

注意:vmstat的采样是“内核级”的。当你执行vmstat 1,内核会启动一个定时器,每秒精确地收集一次上述所有计数器的快照。因此,vmstat的wa和sar的%iowait在原理上高度一致,但vmstat的wa是一个粗粒度的“系统级等待”,而sar可以提供更精细的时间分布。

2.3mpstat:CPU 核心的“CT 扫描”——它关心的是“哪一块在烧”

如果说vmstat是拍一张全身 X 光片,mpstat(multi-processor statistics)就是给每个 CPU 核心做一次高精度 CT 扫描。它的核心价值,在于揭示 CPU 资源的横向分布不均性。

mpstat的输出中,最关键的是-P ALL参数带来的多行结果:

Linux 5.15.0-101-generic (host) 04/15/2024 _x86_64_ (8 CPU) 02:30:01 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 02:30:02 PM all 12.38 0.00 3.12 0.25 0.00 0.12 0.00 0.00 0.00 84.13 02:30:02 PM 0 92.00 0.00 5.00 0.00 0.00 0.00 0.00 0.00 0.00 3.00 02:30:02 PM 1 2.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 97.00 ...
  • all行是加权平均,与top的全局值接近。
  • 后面每一行(0,1, ...)代表一个物理 CPU 核心(或逻辑 CPU,取决于超线程设置)的独立统计。

mpstat的%usr、%sys等字段,其计算逻辑与sar完全相同,都是基于/proc/stat中的cpuN行数据。/proc/stat文件里,每一行cpuN xxx yyy zzz ...记录了该核心自系统启动以来,在用户态、内核态、空闲、I/O 等各种状态所消耗的“jiffies”(内核时间片,通常是 10ms)。mpstat通过两次读取/proc/stat的差值,再除以总 jiffies 差值,就能得到精确的百分比。

为什么这个“分核统计”如此重要?因为现代应用的并发模型(如 Reactor、Actor)和硬件特性(NUMA、超线程)决定了负载天然不均衡。一个典型的 Kafka Broker,其网络 I/O 线程(kafka-network-thread)很可能被绑定在 CPU0 上,而日志刷盘线程(kafka-log-cleaner)则绑定在 CPU4。如果你只看top的 40% 平均值,会误以为一切正常;但mpstat会清晰地告诉你:CPU0 已经 99% 满载,而 CPU7 却只有 5% 利用率。这直接指向了两个优化方向:要么调整线程绑核策略(taskset),要么检查 CPU0 是否存在硬件故障(如降频)。

2.4sar:历史回溯的“黑匣子”——它关心的是“过去发生了什么”

sar(system activity reporter)是性能分析领域的“时光机”。它本身并不实时采集数据,而是依赖一个后台守护进程sysstat(通常叫sadcd)在后台持续、低开销地将系统指标写入二进制日志文件(如/var/log/sysstat/saXX)。当你执行sar -u 1 3,它其实是在读取sadcd刚刚写入的日志,并进行格式化输出。

sar的强大之处在于其历史纵深和采样精度:

  • 历史回溯:sar -u -f /var/log/sysstat/sa15可以查看 15 号当天的完整 CPU 使用率曲线,精度可达 10 分钟一个点(由sadcd的配置决定)。这对于事后复盘线上事故至关重要。你不需要在问题发生时“恰好”开着top,sar的日志就是你的证据链。
  • 采样精度可控:sar -u 1 3表示“每秒采样一次,共采样三次”,这个“1 秒”是严格定时的,由sadcd的内核定时器保证,不受用户态程序渲染延迟影响。因此,sar的瞬时值比top更可靠。
  • 指标维度丰富:sar不仅能看 CPU(-u),还能看内存(-r)、网络(-n DEV)、磁盘(-b)、进程(-q)等,所有指标都来自同一套内核计数器,保证了数据的一致性和可比性。

sar的 CPU 输出(%usr,%sys,%iowait,%idle)与mpstat完全同源,都来自/proc/stat。但sar的%iowait有一个关键细节:它只在 CPU 处于空闲(idle)状态,且有进程在等待 I/O 时,才会计入。这意味着,如果 CPU 正在满负荷运行用户代码(%usr=100%),即使此时磁盘 I/O 队列已排起长龙,%iowait也会是 0。因为 CPU 根本没空“等”,它一直在“干”。这解释了为什么有时vmstat的wa很高,而sar的%iowait却很低——vmstat的wa是一个更宽泛的“系统等待”概念,而sar的%iowait是一个严格的“CPU 空闲等待 I/O”定义。

3. 实操指南:从“看到数字”到“读懂故事”的完整闭环

3.1 标准化诊断流程:五步法定位 CPU 瓶颈

不要一上来就top。一个经过千锤百炼的标准化流程,能让你在 3 分钟内锁定问题根源。这是我在线上事故处理中总结出的“五步法”,已在多个高并发项目中验证有效:

第一步:看vmstat的r和b—— 判断瓶颈类型

# 每秒采样,持续 10 秒,重点关注 r 和 b 列 vmstat 1 10
  • 如果r值持续 > CPU 核心数:CPU 计算资源争抢。进入第二步。
  • 如果b列频繁出现 > 0:I/O 或内核锁瓶颈。跳转到第四步,重点查iostat和pidstat -w。
  • 如果r和b都很低,但系统很慢:可能是内存问题(swap)或网络问题。查vmstat的si/so和sar -n DEV。

第二步:用mpstat -P ALL查“热点核心”

# 查看所有核心的实时负载 mpstat -P ALL 1 3
  • 如果all行的%usr很高(> 70%),但各核心负载极度不均(如 CPU0=95%, CPU7=5%):线程绑核或 NUMA 亲和性问题。用ps -mo pid,tid,%cpu,psr,args -p <PID>查看具体线程在哪个 CPU 上运行。
  • 如果所有核心负载都均匀且很高:真正的计算密集型瓶颈。进入第三步,用top定位进程。

第三步:用top定位“罪魁祸首”进程

# 启动 top,按 P 排序(%CPU),按 H 切换线程视图 top -H -p <PID>
  • 在top中,按P键确保按 CPU 使用率排序。找到PID最高的那个进程。
  • 按H键切换到线程视图(Threads),再次按P。这能让你看到是进程内的哪个线程在疯狂吃 CPU。
  • 按c键显示完整命令行,确认是不是你的业务进程,还是某个系统守护进程(如rsyslogd、auditd)。
  • 记下 PID,准备下一步火焰图分析。

第四步:用pidstat深挖进程行为

# 查看指定进程的详细 CPU 和 I/O 统计 pidstat -p <PID> -u -r -d 1 3
  • -u:显示 CPU 使用率(区分 usr/sys)。
  • -r:显示内存使用(RSS、%MEM)。
  • -d:显示 I/O 统计(kB_rd/s, kB_wr/s)。
  • 这个组合能告诉你:这个高 CPU 进程,是不是同时也在疯狂读写磁盘?如果是,那它高 CPU 的原因,很可能是 I/O 等待后的补偿性计算(比如解压缩、加密),而非纯计算。

第五步:用perf或async-profiler生成火焰图

# 对 Java 进程,推荐 async-profiler(更轻量) ./profiler.sh -e cpu -d 30 -f /tmp/flamegraph.svg <PID> # 对通用进程,用 perf(需安装 kernel-debuginfo) perf record -g -p <PID> -o /tmp/perf.data -- sleep 30 perf script -F comm,pid,tid,cpu,trace,period > /tmp/perf.folded ./FlameGraph/stackcollapse-perf.pl /tmp/perf.folded | ./FlameGraph/flamegraph.pl > /tmp/flamegraph.svg
  • 火焰图(Flame Graph)是终极武器。它能直观地告诉你,CPU 时间究竟花在了哪些函数调用栈上。是卡在String.indexOf()的暴力搜索?还是陷在HashMap.get()的哈希冲突里?抑或是JVM的G1 Young GC导致的 STW(Stop-The-World)?
  • 我见过最经典的案例:一个top显示 90% CPU 的 Java 服务,mpstat显示所有核心均匀负载,pidstat显示它几乎没有 I/O。火焰图一展开,95% 的时间都堆在java.lang.Thread.sleep(Native Method)上。真相是:一个错误的while(true) { if (!condition) Thread.sleep(1); }循环,让线程在 1ms 的休眠和唤醒间疯狂切换,sleep本身不耗 CPU,但频繁的上下文切换(cs)和内核调度开销,把 CPU 吃得干干净净。top只看到了“线程在运行”,却看不到它运行的只是sleep的系统调用入口。

3.2 关键参数详解与实操陷阱

vmstat的隐藏开关:-a和-S
  • vmstat -a:显示 active/inactive 内存页,而不是默认的buff/cache。对于调试内存回收(kswapd)行为非常有用。
  • vmstat -S M:将内存单位强制设为 MB(默认是 KB)。在内存很大的服务器上,-S M能让输出一目了然。-S K(KB)和-S G(GB)同理。
mpstat的核心技巧:-I和-A
  • mpstat -I SUM:显示所有中断(IRQ)的汇总统计,而不是每个中断号的明细。-I COUNT则显示每个 IRQ 的触发次数,可用于定位网卡中断风暴(如eth0-TxRx-0过高)。
  • mpstat -A:显示所有可用的 CPU 统计,包括intr/s(每秒中断数)、ctxt/s(每秒上下文切换数)等高级指标。这是mpstat的“专家模式”。
sar的日志管理:sa1和sa2
  • sa1是sadcd的原始二进制日志,体积小,保留时间长(默认 28 天)。
  • sa2是sadcd每天生成的文本摘要日志(/var/log/sysstat/sa2),方便快速浏览当日峰值。
  • 修改保留策略:编辑/etc/cron.d/sysstat,调整find /var/log/sysstat -type f -name 'sa[0-9][0-9]' -mtime +28 -delete中的+28。
top的救命快捷键
  • 1:切换显示所有 CPU 核心的负载(类似mpstat -P ALL的简化版)。
  • f:进入字段管理界面,可以添加/删除显示列。强烈建议勾选PGRP(进程组 ID)、UID(用户 ID)、TIME+(累计 CPU 时间)。
  • z:开启/关闭彩色模式,让高负载进程一目了然。
  • W:将当前的top配置(排序方式、显示字段等)写入~/.toprc,下次启动自动加载。

3.3 一次完整的线上故障复盘实录

某天下午 2:15,监控告警:某核心支付服务的 P95 响应时间从 80ms 突增至 1200ms。我们立刻登录服务器,执行标准五步法:

Step 1:vmstat 1 5

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 12 0 0 123456 78901 234567 0 0 123 456 1234 5678 15 25 50 10 0 14 0 0 123000 78901 234567 0 0 123 456 1234 5678 18 28 45 9 0 16 0 0 122500 78901 234567 0 0 123 456 1234 5678 22 32 38 8 0

r值从 12 持续飙升到 16,而b=0,wa=8%。结论:纯 CPU 争抢,无 I/O 瓶颈。

Step 2:mpstat -P ALL 1 3

02:16:01 PM CPU %usr %sys %iowait %idle 02:16:02 PM all 45.38 32.12 0.25 22.25 02:16:02 PM 0 92.00 45.00 0.00 3.00 02:16:02 PM 1 91.00 44.00 0.00 5.00 02:16:02 PM 2 2.00 1.00 0.00 97.00 ...

all行平均 45%,但 CPU0 和 CPU1 的%usr都超过 90%,%sys也高达 44%!这说明问题不在用户代码,而在内核态。高%sys通常意味着大量的系统调用、上下文切换或内核锁竞争。

Step 3:top -H在top中按P排序,发现PID 12345(Java 进程)的%CPU占据榜首。按H切换线程视图,发现TID 12346(主线程)和TID 12347(GC 线程)的 CPU 占用最高。

Step 4:pidstat -p 12345 -u -r -d 1 3

02:16:10 PM UID PID %usr %system %guest %CPU CPU Command 02:16:11 PM 1001 12345 12.38 32.12 0.00 44.50 0 java 02:16:11 PM 1001 12345 15.00 35.00 0.00 50.00 1 java

%usr仅 12-15%,但%system高达 32-35%,证实了内核态开销巨大。

Step 5:perf火焰图生成火焰图后,90% 的样本都集中在do_syscall_64->sys_futex->futex_wait_queue_me。futex是 Linux 的快速用户空间互斥锁,futex_wait_queue_me表明线程正在等待一个锁。结合代码,我们发现一个全局的ConcurrentHashMap被错误地用于高频的putIfAbsent操作,导致在高并发下,大量线程在futex系统调用上自旋等待,%sys爆表。

根因与修复:将ConcurrentHashMap替换为更细粒度的分段锁,或改用LongAdder等无锁数据结构。上线后,%sys从 35% 降至 5%,P95 响应时间回到 80ms。

4. 常见问题与独家避坑指南:那些文档里不会写的实战教训

4.1 “为什么top和sar的 CPU 使用率总是差一大截?”

这是最常被问到的问题。答案藏在它们的采样机制和统计口径里。

工具采样方式统计对象关键差异
top用户态轮询/proc/[pid]/stat,刷新间隔不严格所有 R/S 状态进程的utime/stime总和忽略 D 状态进程;受终端渲染延迟影响;默认只显示“活跃”进程(top会过滤掉ppid=0的内核线程)
sar内核定时器驱动,读取/proc/stat的cpu行内核维护的全局 CPU 时间片计数器包含所有状态;采样精准;%iowait仅在 CPU idle 且有 I/O 等待时计数

实操验证:

# 启动一个会进入 D 状态的测试进程(模拟 I/O 等待) dd if=/dev/sda of=/dev/null bs=1M count=1000 & # 在另一个终端,同时运行 top -b -n1 | grep "Cpu(s)" # 记录 top 的 %id sar -u 1 1 | tail -1 # 记录 sar 的 %idle

你会发现,top的%id可能是 95%,而sar的%idle可能只有 60%。因为dd进程大部分时间在 D 状态等待磁盘,top认为它“没在用 CPU”,所以空闲率虚高;而sar的%idle是从系统时间片角度计算的,dd的等待时间被计入了%iowait,从而拉低了%idle。

提示:在生产环境,永远优先相信sar的历史数据和vmstat的r/b列。top只是一个优秀的“进程探针”,不是一个可靠的“系统仪表盘”。

4.2 “mpstat显示 CPU0 100%,但top里找不到任何进程在 CPU0 上运行!”

这听起来像玄学,但其实非常常见。原因有两个:

第一,top默认不显示内核线程。Linux 内核会创建大量kthreadd派生的内核线程,如ksoftirqd/0(CPU0 的软中断处理线程)、migration/0(CPU0 的进程迁移线程)、rcu_preempt(RCU 回调线程)。这些线程的PID通常很小(< 100),且PPID是2(kthreadd),top的默认过滤规则会把它们隐藏。解决方法:在top中按f,找到PPID字段并启用它,然后按P排序,你就能看到PPID=2的内核线程了。

第二,mpstat的%usr/%sys是“时间片占比”,而top的%CPU是“进程级 CPU 时间占比”。一个内核线程可能在 1 秒内被调度了 1000 次,每次只运行 1ms,top的%CPU可能只显示 1%;但mpstat会把它这 1000ms 的总和,算作 CPU0 的 100% 占用。所以,当你看到mpstat的 CPU0 100%,而top里没有高 CPU 进程时,第一反应应该是:ps -eo pid,ppid,comm,%cpu --sort=-%cpu | head -20,专门找PPID=2的内核线程。

4.3 “vmstat的wa是 0,但磁盘 I/O 很高,iostat显示%util100%,这不矛盾吗?”

完全不矛盾。vmstat的wa(I/O wait)和iostat的%util(设备利用率)是两个维度的指标。

  • vmstat wa:表示CPU 在等待 I/O 完成时所花费的时间占比。它要求两个条件同时满足:1) CPU 处于 idle 状态;2) 有进程在等待 I/O。如果 CPU 正在满负荷运行用户代码(%usr=100%),即使磁盘队列已满,wa也是 0。
  • iostat %util:表示块设备(如 /dev/sda)在采样周期内,忙于处理 I/O 请求的时间百分比。%util=100%意味着磁盘的请求队列从未空闲过,它一直在干活。

场景还原: 想象一个 CPU 强大的数据库服务器,它向磁盘发出了海量的随机小 I/O 请求。由于 CPU 太快,它发出请求后,立刻就去干别的事了(比如解析下一个 SQL),根本不等。磁盘在后面吭哧吭哧地处理,iostat的%util一路飙到 100%。而 CPU 一直忙着计算,vmstat的wa自然就是 0。这时,瓶颈在磁盘,但wa=0并不表示“没问题”,它只是说明“CPU 没闲着等”。

注意:iostat的await(平均 I/O 等待时间)和svctm(平均服务时间)才是判断磁盘是否真的慢的关键。如果await远大于svctm,说明请求在队列里等了很久,是典型的 I/O 队列过长。

4.4 “sar报告%iowait很高,但iostat显示磁盘r/s和w/s很低,怎么回事?”

这通常指向一个经典陷阱:网络文件系统(NFS)或分布式存储(如 Ceph)的延迟伪装。

%iowait的定义是“CPU idle 且有进程在等待 I/O”。对于本地磁盘,等待 I/O 几乎等同于等待磁盘。但对于 NFS,一个read()系统调用,可能需要等待网络往返(RTT)、远程服务器的处理、甚至远程服务器的磁盘 I/O。sar会忠实地把这个漫长的等待,记为%iowait,因为它符合“CPU idle + 等待 I/O”的定义。但iostat只监控本地块设备,它对 NFS 的网络延迟一无所知,所以r/s

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询