news 2026/9/7 17:37:27

别被top骗了!CPU使用率低但load高?一文读懂Linux性能排查核心指标

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别被top骗了!CPU使用率低但load高?一文读懂Linux性能排查核心指标

作为天天跟 Linux 服务器打交道的运维,我几乎每天都要打开 top 看那么几眼。但说实话,用久了你会发现一个很诡异的现象:有时候 top 里 CPU 使用率明明不高,才 20% 多,系统却卡得跟幻灯片一样;反过来,有时候 CPU 都飙到 90% 了,业务却一点毛病没有。如果你也遇到过这种情况,那说明你已经被 top 这层皮给骗了。

top 只是给你看了一张快照,真正决定系统"卡不卡"的,是 CPU 使用率背后那三个纠缠在一起的东西:负载(load average)、内存带宽(DDR)和 IO。这篇文章我想把我这些年排查性能问题的实战经验掰开揉碎讲清楚——不仅仅是看懂 top 的输出,而是搞明白从 CPU 到内存再到磁盘这条链路上,瓶颈到底卡在哪个环节。

1. 从 top 第一行开始:CPU 使用率只是表象

1.1 top 里的 CPU 百分比到底是怎么算出来的

很多人看 top,上来就盯 %Cpu(s) 这一行,看到 us 30%、sy 5%,就觉得机器挺闲。这个直觉在绝大多数场景下是错的。top 显示的不是"CPU 有多忙",而是"CPU 在过去一个刷新周期里,有多少时间花在了非空闲任务上"。

Linux 内核会为每个 CPU 维护一组时间计数器,分别记录:

  • user(us):用户态进程消耗的时间
  • system(sy):内核态消耗的时间
  • nice(ni):被调整过优先级进程消耗的时间
  • idle(id):完全空闲的时间
  • iowait(wa):等待 IO 完成的时间
  • irq/softirq(hi/si):硬中断和软中断消耗的时间
  • steal(st):在被虚拟化环境中,被 hypervisor 抢走的时间

top 默认每 3 秒刷新一次,它做的事情就是取两个时间点的差值,用非空闲时间除以总时间,得到一个百分比。也就是说,你看到的 30% 是"这 3 秒里 CPU 有多少比例的时间非空闲",而不是"CPU 的平均负载程度"。

关键问题就在这里:只有 us 和 sy 高,才代表 CPU 算力真的在被消耗。如果 wa(iowait)占了 30%,CPU 实际上是闲着的,它在等磁盘、等网络、等内存,这种"忙"是虚假的忙。

注意:top 里的 wa 列是整个 Linux 性能排查里最容易被误读的指标之一。它高,说明 CPU 在空转等待 IO,你的系统瓶颈大概率在存储层面,而不是计算层面。

1.2 瞬时快照 vs 时间均值:top 天生带"近视眼"

top 的另一个大坑是它只看瞬间。你看到 CPU 使用率 40%,但实际这 3 秒里可能有 2 秒 CPU 打满了,另 1 秒在休眠。对于延迟敏感的在线业务,这种抖动是致命的,但 top 的采样周期根本看不出来。

这也是为什么我强烈建议在排查问题时不要单看 top。sar 和 atop 这类工具会做完整的时间序列采集。sar -u 1 10 可以每秒采样一次输出均值,atop 则能以 10 秒为单位记录每核每进程的完整历史。生产环境里,我通常会在服务器上长期挂着 atopsar,出事的时候直接回放当时的 CPU、内存、磁盘、网络全景,比对着 top 猜要靠谱一万倍。

实操建议:top 只适合"快速看一眼",不适合"深入定位问题"。一旦 top 显示异常,立刻切到 sar/vmstat/pidstat 去拿连续时间维度的数据。

1.3 多核场景下,top 的百分比是怎么骗人的

在 32 核的机器上,top 显示的 %Cpu 是"所有核的平均值"。假设只有 4 个核被打满了,另外 28 个核闲着,top 显示的是 12.5%,看起来毫无压力。但应用如果是单线程或者线程数很少的,它就卡在那 4 个核上了。

所以看 CPU 使用率,我基本不看汇总行,而是进 top 后按一下数字键 1,把每个核单独列出来看。如果某一两个核持续 100%,其他核比较低,说明有线程绑定或者热线程集中的问题;如果所有核都很均匀地高,才说明并行负载起来了。

还有一个进阶技巧:top -H 可以切换到线程视图,找到消耗 CPU 最高的线程 ID,然后用 pthread 库或者 Java 的 jstack 去反查代码层是哪个线程在作妖。这是排查 CPU 问题的标准动作。

2. load average:那个"1.5"到底是什么意思

2.1 别再管 load 叫"CPU 使用率"了,它是任务队列长度

top 右上角第一行有个 load average: 1.50, 2.00, 2.50,很多人以为这是 CPU 使用率的平均值。大错特错。load 是内核运行队列上的"可运行进程数 + 不可中断进程数"的平均值。

我来拆一下这两个"数":

  • 可运行进程(TASK_RUNNING):正在 CPU 上跑的,或者在就绪队列里等着 CPU 调度的进程
  • 不可中断进程(TASK_UNINTERRUPTIBLE):正在等 IO 完成的进程,通常是在等磁盘读写,这种状态在 ps 里显示为 D

内核会每 5 秒把这个数值算一次,然后分别取 1 分钟、5 分钟、15 分钟的移动平均,就是你看到的 load average 的三个值。

关键结论来了:一个进程只要在等待磁盘 IO,它就会一直挂在"D 状态",持续占用一个 load 名额。cpu 使用率低,IO 慢,load 照样能冲到 20 以上。

2.2 为什么 D 状态进程会让 load 飙到一个离谱的数字

我们之前排查过一台数据库服务器,现象特别经典:CPU 使用率 15%,load 却飙到了 35。大家一开始都以为是某些进程死循环了,用 top 按 CPU 排序一看,排在前面的是几个 PostgreSQL 后台进程,CPU 占用才 1%。

真正的问题出在哪?用 ps -eo pid,state,cmd 一看,大量进程的状态是 D。它们全在等一块慢到家的机械硬盘做数据落盘。这些 D 状态进程既不能被 kill,也不会被调度器"放假",就硬生生地占着 load 名额。

内核为什么这么设计?因为不可中断状态是为了保证进程在等待 IO 完成时不会丢失数据,如果强行中断,IO 回来的数据就没人接收了。这也是为什么 D 状态进程连 kill -9 都杀不掉——内核根本不响应这个信号,只能等 IO 超时,这是保护机制,但也是 load 虚高的根源。

2.3 判断 load 是否正常的经验法则

很多文章说 load 的及格线是 CPU 核数,四核机器 load 超过 4 就要报警。这个说法其实太粗糙了。我的经验是分场景看:

  • 纯计算型任务:load 超过核数*0.8 就该警惕了,说明 CPU 快饱和了
  • IO 密集型任务:load 可以超过核数很多,因为大部分时间 CPU 在空闲等待 IO,此时要结合 iowait 和磁盘 util 判断
  • 混合型业务(互联网后端的常态):load 和核数的比例在 1 到 1.5 之间问题不大,超过 2 就要看是 CPU 问题还是 IO 问题的分叉了

另外 load 的三个值一定要结合起来看。1 分钟值 > 15 分钟值,说明系统负载在上升,是个异常信号;反过来说明负载在下降,可能刚经历了一次抖动,正在恢复。

注意:只看 load 绝对值没意义,要看趋势。连续几个时间点 load 都在攀升,才是需要干预的时候。

2.4 用 vmstat 验证 load 的构成,别只盯着 top

要搞清楚 load 到底是 CPU 撑起来的还是 IO 撑起来的,我通常直接上 vmstat。重点关注两列:r(running)和 b(blocked)。

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 1 2 0 408960 12340 4098600 0 0 12 64 0 0 5 2 78 15 0 3 4 0 406920 12340 4098800 0 0 256 1024 1200 8000 10 5 55 30 0
  • r 列是正在运行和等待 CPU 的进程数。这个值长期大于核数,说明 CPU 确实是瓶颈
  • b 列是处于 D 状态(阻塞在 IO)的进程数。b 列显著大于 0,尤其大于 r 列时,说明 load 主要是 IO 撑起来的

那一行我一直记着:r 是 CPU 排队的人数,b 是 IO 排队的人数。load 高的时候先分清楚是哪个队伍排长队了,再动手去解决,方向才不会跑偏。

3. DDR 内存带宽:CPU 算不过来还是数据喂不进来

3.1 CPU 和内存之间那条"水管"才是真瓶颈

很多人有个误解:CPU 使用率高 = 瓶颈在 CPU。其实不对。CPU 使用率高的另一种可能是,CPU 一直在等内存数据。

这里要理清一个概念:CPU 内部有缓存(L1/L2/L3),缓存没命中才去访问内存(DDR)。访问 L1 缓存大约 1ns,访问 L3 大约 10ns,访问内存则要 100ns 量级。一旦算法或数据布局导致缓存命中率差,CPU 就大量地陷入"等内存"的状态。

CPU 厂商为了解决这个问题,搞了乱序执行、超标量流水线、预取器。看起来 CPU 在满负荷运转,实际上指令队列里有大量空泡——都在等内存的数据回来。此时 top 的 us 可能还是 80% 多,但真正的算力产出很低,为什么?因为 CPU 的"执行单元"大部分时间是闲置的,只是流水线前端一直在忙碌地发射指令。

3.2 内存带宽怎么算,又该怎么侧

DDR 内存有标称带宽,计算方式很简单:DDR4-2666 的理论带宽 = 2666 MHz × 8 字节 × 2(双倍速率)≈ 42.6 GB/s。但这是理论峰值,实际能用到的通常只有理论值的 60%-80%,原因包括内存控制器开销、地址映射开销、ECC 校验等。

判断应用是否在吃内存带宽,简单粗暴的方法是看 CPU 的访存指令比例。用 perf stat 可以看真实数据:

perf stat -e cycles,instructions,cache-references,cache-misses -p PID

重点关注 cache-misses 的占比。如果 miss 率超过 10%,说明内存访问已经成为瓶颈。另一个更直接的指标叫 CPI(cycles per instruction),即每条指令平均消耗多少 CPU 周期。CPI 越高,说明 CPU 等数据的时间越长。正常计算密集型程序 CPI 在 0.5 到 1 之间,如果超过 2,基本可以断定在等内存。

3.3 NUMA 架构下的内存访问差异

现在服务器几乎都是多路 NUMA 架构。CPU 访问本地内存和远端内存的延迟差别很大。比如 2 路服务器,CPU0 访问自己插槽上的内存条延迟 70ns,访问 CPU1 插槽上的内存则要 130ns 左右,吞吐量差距更明显。

这带来一个很现实的问题:如果你的进程被调度到了 CPU0,但它主要访问的数据在插槽 1 的内存里,性能直接打七折。这个现象在 top 里是完全看不出来的——CPU 利用率照样高,但吞吐量就是上不去。

排查办法:用 numastat -p PID 看进程的内存分配情况。如果 local_node 的命中率很低,说明跨 NUMA 访问严重,可以考虑用 numactl --cpunodebind=0 --membind=0 绑定进程,或者用 numa_alloc_onnode 之类的 API 来优化内存分配策略。

3.4 用压力测试验证内存带宽的上限

如果你怀疑是内存带宽不够,别猜,直接压。stream 是个简单粗暴的内存带宽测试工具,编译完直接跑,能看到 Copy、Scale、Add、Triad 四类操作的实际带宽。

我一般会在新服务器上线前跑一遍 stream,把结果记到资产台账里。这样后面如果觉得"CPU 应该很快但实际很慢",就先跑一遍 stream,看结果是否和初始记录一致。如果带宽明显下降,可能是内存条坏了、降频了,或者 CPU 的温度墙触发导致内存控制器降速了。

在京东上的内存条好几百一根,但内存问题导致的生产故障,损失是以小时以几十万计的,这个账一定要算清楚。

4. IO 到底怎么侵占 CPU 和 load 的

4.1 从一次磁盘读,看整个阻塞链

一条 read() 系统调用从发出到完成,全程是这样的:

  1. 应用进程发起 read,进入内核态
  2. 内核把请求交给文件系统和块设备层
  3. 请求进入磁盘的等待队列,进程被标记为 D 状态(TASK_UNINTERRUPTIBLE)
  4. CPU 此时可以切换到其他进程,但如果所有进程都在等 IO,CPU 就进入了 idle 状态,这个 idle 时长会被记录为 iowait(wa)
  5. 磁盘数据到了,触发中断,进程回到可运行状态,重新排队等 CPU

看到问题了吗?在这个流程中,拓扑上 CPU 是"闲"的,但 load 里挂着那个 D 状态进程,load 是"高"的。所以就会形成标题里说的经典幻觉:CPU 使用率低,load 却高得吓人。

如果业务对读写时延敏感,这个链路里每一个队列都是延迟放大器。从应用层看,只是发起了一个 IO,等待了 100ms,但在系统内部,这 100ms 可能经历了 5 个队列,每个队列都堆积了几十个任务。

4.2 iowait 不等于磁盘慢了,也可能是 CPU 被中断打爆了

iowait 高,第一反应都是"磁盘慢"。但有一种特殊情况:磁盘硬件很快,但 CPU 被海量 IO 中断打满了。尤其是 NVMe SSD 时代,单盘能跑到每秒百万级 IOPS,如果中断处理不当,CPU 会花大量时间处理中断。

怎么区分这两种情况?看 iostat -x 1 输出的 %util 列,这是磁盘"忙"的比例。如果 %util 很高(接近 100%)但 svctm 很低、await 也不高,说明磁盘本身处理能力没问题,是 CPU 处理中断的能力到顶了。这时候的解决方案是调大中断合并(coalescing),或者用多队列 + RPS(Receive Packet Steering)把中断分散到多个核。

另一个极端的坑:某些云厂商虚拟化环境里,iowait 很高,但用 iostat 看磁盘延迟完全正常。这大概率是宿主机上的邻居在抢 IO 资源,你看到的 iowait 是被"偷走"的等待时间,跟你自己的磁盘没半毛钱关系。

注意:判断 IO 问题,务必同时看 CPU 的 wa、磁盘的 %util、await、以及 iostat 里的 aqu-sz(平均队列长度)。只看其中任何一个,都可能得出完全错误的结论。

4.3 典型 IO 瓶颈场景的现场还原

场景一:日志写入拖垮一切。Java 应用使用 log4j2 的异步日志,但底层磁盘是整个系统里最慢的机械盘。高峰时每秒产生 20MB 日志,磁盘顺序写能力只有 80MB/s。这台机器的现象就是:CPU 使用率 5%,load 飘到 10 以上,应用接口超时率飙升。解决方案很简单:换 SSD,或者把日志目录挂到 tmpfs 里,问题直接消失。

场景二:数据库刷脏页。MySQL 的 InnoDB 在内存里改了数据页,后台线程负责把它们刷到磁盘。如果磁盘写入能力不够,脏页比例会持续上升。当比例达到 75%,用户查询也会被迫参与到刷盘里,延迟从 1ms 飙到 500ms。top 看起来什么样?CPU 不高,但进程的 D 状态很明显,load 一天比一天高。

场景三:容器存储的叠加层。容器里的写操作如果落在 overlayfs 的 upperdir,所有读写都要经过宿主机上的存储驱动。常见的做法是把数据目录挂成 volume,绕开容器层,确实是踩过坑之后得出的教训。

4.4 快速定位 IO 瓶颈的命令组合

单靠 top 完全讲不清 IO 问题,我的标准动作是四连招:

  1. top 先看全局,确认是否有异常
  2. vmstat 1 5 看 wa 和 b 列
  3. iostat -x 1 看具体磁盘的 %util、await、w_await
  4. pidstat -d 1 找到具体是哪个进程在产生 IO

pidstat -d 输出示例:

Linux 4.18.0-305.el8.x86_64 10/24/2024 _x86_64_ (32 CPU) 02:43:12 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 02:43:13 PM 1001 12345 0.00 20480.00 0.00 12 mysqld

如果看到某个进程的 kB_wr/s 特别高,iotop 再确认一下是不是它拖累了整个系统,就知道该治谁了。

5. 实战案例:从"top 看起来没事"到真正定位瓶颈

5.1 一次典型的 CPU 低、load 高排查实录

有一回我们一个金融客户的生产环境报障:交易接口响应时间从 50ms 涨到 3s。我登录上去第一件事就是 top:

top - 14:22:01 up 120 days, 2:14, 3 users, load average: 22.50, 21.30, 19.87 Tasks: 320 total, 1 running, 318 sleeping, 1 stopped, 0 zombie %Cpu(s): 8.2 us, 3.1 sy, 0.0 ni, 75.4 id, 12.5 wa, 0.3 hi, 0.5 si, 0.0 st

load 高到 22,CPU 使用率却只有 11%,而且 wa 达到 12.5%。看到这个组合,基本可以断定不是 CPU 算力的问题。继续用 vmstat 1 3:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 18 0 589372 102400 8049800 0 0 1024 819200 0 0 8 3 68 21 0

注意 b 列 18,r 列只有 2。压垮 load 的完全是被 IO 阻塞住的 18 个进程。iostat -x 1 看了下数据盘,%util 直接到了 99%,await 从 5ms 涨到 980ms,aqu-sz 高达 40 多。磁盘请求队列已经堵成一锅粥。

定位到具体进程用 pidstat -d,发现是 MySQL 的 redo log 刷盘和 binlog 同步在争抢磁盘,而磁盘的 IOPS 能力已经到了物理极限。最终方案是把 redo log 和 binlog 分开到两个不同的物理 SSD,再把 innodb_io_capacity 参数从 200 调到跟硬件匹配的 2000。处理完再看,load 降到 3 左右,接口延迟恢复到 40ms。

5.2 反向案例:CPU 使用率 90%,但业务延迟依然稳定

另外一个案例是反向的。一台 64 核的推荐算法服务器,top 显示 CPU 使用率长期在 90% 以上。按照一般的思路,CPU 都快被打满了,应该加机器了吧?

我看了下 qps 曲线和延迟曲线,发现虽然 CPU 高,但 p99 延迟非常稳定,说明这台机器实际上还在能力范围内。进一步用 perf top 看了下 CPU 热点,发现耗时大部分集中在矩阵乘法库上,都是高效的 SIMD 指令,没有浪费。

这时候 CPU 使用率高恰恰说明"资源被用在了刀刃上"。加不加机器,判断依据不应该是 CPU 百分比,而应该是业务的性能指标是否满足 SLO。CPU 高不是问题,CPU 高但延迟抖动、排队堆积,才是问题。

5.3 一套适合自己的日常巡检命令组合

上面是线上出问题的排查思路,但要避免事故发生,日常巡检更重要。我自己惯用的是一套定时任务组合:

  • 每 1 分钟采集一次 sar -u、sar -r、sar -d,保留 30 天
  • 每 10 秒用 atop 记录一次全量资源快照,保留 7 天
  • 每周跑一遍 stream 内存带宽测试,对比基线
  • 每月用 pidstat 拉一次 TOP10 进程的历史系统资源消耗,观察业务变化趋势

这套东西你可以在每台机器上配置,也可以接入 Prometheus 那套体系。关键是坚持记录,因为性能问题大多是慢慢恶化的,没有基线数据,等到量变引发质变的时候,你都说不清是哪天开始变慢的。

5.4 常见误导场景速查表

现象容易得出的错误结论实际可能原因正确排查命令
CPU 0%,load 20机器没事D 状态进程在等 IOvmstat 的 b 列、ps 看 D 状态
CPU 90%,应用慢CPU 不够用内存带宽打满/缓存 miss 率太高perf stat 的 cache-misses、stream
CPU 30%,wa 30%磁盘慢磁盘不慢,CPU 中断处理不过来iostat 的 %util 和 svctm 对比
单核 100%,整体 20%整体没有瓶颈单线程热点top 按 1 键看每核,pidstat -t 查线程
load 15,核数 32完全没压力队列分配不均,部分核过于繁忙mpstat -P ALL 1 看单核分布

6. 工具链全景:从 top 出发,构建自己的排查体系

6.1 各层级工具的定位和选择

性能排查工具大体分三层:

  • 全局视角层:top、vmstat、sar、atop。看的是系统整体资源状态
  • 进程视角层:pidstat、htop、iotop。定位到具体进程
  • 内核/指令视角层:perf、bpftrace。深到 CPU 指令级、内核函数级的分析

我见过不少人排查性能问题只会用 top,碰到怪问题就发愁。我的建议是:top 之下的每一个命令,都值得花一个下午去深入练习,比如 vmstat 的每一列,iostat 的每一列,都是内核某个子系统的统计输出,弄懂它们背后的原理,比记住命令本身重要得多。

6.2 动态追踪工具给传统排查带来极大便利

看 perf 或者 bpftrace 的输出,有时候会劝退新手,但这类工具才是性能排查的大杀器。举个例子:前几年我们用 bpf 的 offcputime 工具,统计进程在等待 IO 时究竟卡在哪一棵内核调用树上,一眼就看到 NFS 客户端的 rpc_wait 占了 60% 的等待时间。如果是用传统工具一层层查,可能要好几天。

不过,动态追踪工具是"高手向"的玩法,新手建议先把 vmstat、iostat、pidstat、sar 这几个基本功练扎实,形成"从全局到局部"的排查思维,再上手 bpf 工具,效率会高很多。

6.3 建立自己的性能基线库

最后想重点聊聊基线这件事。很多人排查问题的时候,最大的困难不是找不到工具,而是"不知道正常值应该是多少"。16 核的机器 load 到 8 正常不正常?你的数据库磁盘 iops 消耗到 5000 是不是上限?这些问题如果心里没有谱,排查起来就是大海捞针。

我的做法是每台服务器上线后,在业务低峰期用 sysbench、fio、stream 分别跑一轮 CPU、磁盘、内存的基准测试,记录到台账里,之后只需对比和基线的偏离程度,性能恶化的苗头就能第一时间发现。这套方法你任何时候任何环境都可以用,它才是真正能让你"不再被 top 骗"的根本手段。

踩过的坑多了之后,我最大的感触是:top 只是个入口,它负责把你引到问题现场,至于真凶是谁,还是要靠对整个系统运行机制的透彻理解,以及一套顺手的工具组合去深挖。希望这篇分享能给你提供一条清晰的排查路径,别再让 top 上的数字带偏方向。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 17:37:24

SpringBoot+小程序开发海洋环保系统实战

1. 项目背景与核心价值海洋环保小程序系统是一个基于SpringBoot框架开发的轻量级应用,旨在通过移动互联网技术提升公众参与海洋环境保护的便捷性。这个项目最吸引我的地方在于它巧妙地将环保理念与技术实现相结合——用户可以通过小程序随手拍摄并上传海洋污染情况&…

作者头像 李华
网站建设 2026/9/7 17:24:11

Git冲突解决实战:从理解合并本质到从容处理代码分歧

1. 先别急着学命令,把「冲突」这件事想明白很多人一遇到 Git 冲突就条件反射地开始背命令,git merge --abort、git checkout --ours、git rebase --continue——仿佛冲突是个 Bug,只要命令用得够快,它就会消失。但我做了几年的代码…

作者头像 李华
网站建设 2026/9/7 17:23:32

LangChain多智能体实战:用LangGraph编排Agent构建婚礼策划系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 17:23:23

FlagOS深度解析:一套开源软件栈如何打通异构算力算子库

算力荒折腾了两年,我现在最深的体会是:大模型训练和部署的瓶颈早就不单是卡本身,而是软件栈被各家芯片厂商牢牢锁死。A卡一个生态,B卡一个生态,C卡又是一个半成品生态,每换一批卡就要把框架、算子、通信库重…

作者头像 李华