load 高达 28 CPU 却闲着:一次 Linux 负载与 iowait 排查复盘

一台 4 核服务器服务变慢,top 看 load average 高达 28,可 CPU 空闲居然有 90% 多,负载爆表和 CPU 闲着直接矛盾。排查梳理:load average 不是 CPU 利用率,它统计的是在跑加排队等 CPU 加卡在 D 状态等磁盘的进程总数,所以 load 高有 CPU 不够和磁盘 IO 不够两种成因;看到 load 高第一件事是看 top 的 CPU 行,us 加 sy 高才是 CPU 过载,wa 也就是 iowait 高是 CPU 闲着却被等磁盘的进程拖住瓶颈在磁盘;确认 IO 瓶颈用 iostat -x 看 %util 和 await,找狂读写磁盘的进程用 iotop -oP 或 pidstat -d;D 状态是不可中断睡眠几乎都在等磁盘连 kill -9 都杀不掉但照样算进 load,治 load 要去治磁盘 IO 不是杀进程;别把内存不足狂换 swap 误判成 IO 问题,缓解靠从源头减少不必要的写、改异步批量、给非关键任务 ionice 降优先级,监控要把磁盘指标也纳入告警,以及一套负载与 IO 排查纪律。

2024 年的一个上午,一个服务变得很慢,接口响应从几十毫秒涨到了好几秒。我登上那台 4 核的服务器,习惯性地敲了 top,第一眼就看到了 load average:28.5、26.1、22.0。我心里咯噔一下——4 个核,负载却到了 28,这不是把 CPU 往死里压吗?我下意识就准备去找那个把 CPU 吃满的进程。可当我把目光往上挪,挪到那一行 CPU 占用统计时,我整个人愣住了:%us 用户态才 3%,%sy 内核态 2%,%id 空闲——居然有 90% 多。一台 CPU 闲着 90% 的机器,load average 怎么会是 28?负载高到爆表,CPU 却悠闲得很,这两件事像是直接矛盾的。我又看了 uptime,又看了好几遍 top,数字纹丝不动:load 就是 28,CPU 就是闲着。我盯着这一对矛盾的数字想了很久,最后才反应过来,我从一开始就把 load average 理解错了——我一直以为它就是"CPU 有多忙"的意思,可它数的,根本不只是 CPU。这件事逼着我把 Linux 的 load average、iowait、D 状态进程这一整套彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,4 核的服务器,跑着一个读写比较重的服务
事故现象:
- 服务接口响应变慢,从几十毫秒涨到几秒
- ★ load average 高达 28(机器才 4 核)
- ★ 可是 CPU 空闲(idle)居然有 90% 多 —— 和 load 矛盾

现场排查:
# 1. 看负载 —— 高得吓人
$ uptime
 10:22:01 up 96 days,  load average: 28.50, 26.10, 22.00   # ★ 4 核 load 28

# 2. ★ 看 CPU —— 居然闲着
$ top
%Cpu(s):  3.0 us,  2.0 sy,  0.0 ni, 28.0 id, 67.0 wa, 0.0 hi
#                                    ^^^^^ id 空闲   ^^^^^ ★ wa 高达 67%!
# ★ CPU 不忙,但有 67% 的时间在 "wa" —— iowait。

# 3. ★ 看有多少进程卡在 D 状态
$ ps -eo stat,pid,comm | grep '^D' | wc -l
24                                            # ★ 24 个进程是 D 状态!

# 4. ★ 看磁盘 IO —— 磁盘被打满了
$ iostat -x 1 3
Device   ...  %util
vda      ...  99.7                            # ★ 磁盘利用率 99.7%,满了

根因(后来想清楚的):
1. ★ load average 不是"CPU 利用率"。它统计的是
   【需要被处理、但还没处理完】的进程数量,具体包括:
   - 正在 CPU 上跑的进程(R)
   - 排队等 CPU 的进程(R,可运行但没轮到)
   - ★ 卡在【不可中断睡眠】的进程(D)—— 几乎都在等磁盘
2. ★ 所以 load 高,有两种完全不同的成因:
   - CPU 忙不过来 -> 一堆进程排队等 CPU(这是经典理解)
   - ★ 磁盘忙不过来 -> 一堆进程卡 D 状态等磁盘 IO
3. 我这次是第二种:磁盘被打满(%util 99.7%),
   24 个进程全卡在 D 状态干等磁盘把数据读/写完。
4. ★ iowait(top 里的 wa)就是:CPU 本身闲着、
   没活干,但又【没法往下走】,因为它在等 IO 完成 ——
   67% 的 wa,说明 CPU 三分之二的时间都在干等磁盘。
5. 于是怪现象解释通了:CPU 真的闲(idle/wa 占大头),
   但等磁盘的进程堆成山,load average 就被顶到了 28。
6. 真正的瓶颈是【磁盘 IO】,不是 CPU。盯着 CPU 找,
   永远找不到病根。
load 高先别急着怪 CPU,先看 CPU 到底忙不忙、wa 高不高。

修复 1:load average 高 ≠ CPU 忙——它数的是什么

# === ★ 先纠正最核心的误解:load average 到底是什么 ===

# === load average 不等于 CPU 利用率 ===
# 几乎人人都把 load average 当成"CPU 有多忙"。★ 不对。
# load average 统计的是:在某个时刻,系统里【需要被
#   处理、但还没处理完】的进程的【平均数量】。
$ uptime
 load average: 28.50, 26.10, 22.00
# ★ 三个数 = 过去 1 分钟 / 5 分钟 / 15 分钟的平均值。

# === ★ 关键:哪些进程会被算进 load ===
# Linux 的 load,把这几类进程都算进去:
#  1. R(Running):正在 CPU 上跑的
#  2. R(Runnable):可以跑、就等一个 CPU 核空出来
#  3. ★ D(Uninterruptible Sleep):不可中断睡眠 ——
#     这类进程【绝大多数是在等磁盘 IO 完成】。
# ★ 第 3 类,正是大多数人对 load 的认知盲区。

# === ★ 于是 load 高有【两种】完全不同的病因 ===
# 病因 A:CPU 不够 —— 一堆 R 进程排队等 CPU。
#   这时 CPU 利用率会很高(us + sy 很高)。
# 病因 B:★ 磁盘/IO 不够 —— 一堆 D 进程等磁盘。
#   这时 CPU 利用率可能很低,但 wa(iowait)很高。
# ★ load 这一个数字,A、B 两种病都会让它变大 ——
#   所以光看 load,根本分不清是哪种病。

# === ★ 看到 load 高,第一件事是看 CPU 忙不忙 ===
$ top    # 看 %Cpu(s) 那一行
# %us(用户态)+ %sy(内核态)高 -> 病因 A,CPU 真忙
# ★ %id(空闲)或 %wa(iowait)高 -> 病因 B,CPU 没事,
#    瓶颈在 IO。我这次就是 wa 高达 67%。

# === 用 load 和 CPU 核数对比来"读"负载 ===
$ nproc
4
# ★ load 大致和核数比着看:load ≈ 核数 = 满载;
#   load 远大于核数,【且 CPU us+sy 高】= CPU 过载;
#   load 远大于核数,【但 CPU 闲】= 不是 CPU 的事,查 IO。

修复 2:iowait 是什么——CPU 在"干等磁盘"

# === ★ 把 iowait(wa)这个概念彻底讲清楚 ===

# === top 里 %Cpu(s) 那一行的每一项 ===
$ top
%Cpu(s): 3.0 us, 2.0 sy, 0.0 ni, 28.0 id, 67.0 wa, 0.0 hi, 0.0 si
# - us :CPU 跑用户程序的时间
# - sy :CPU 跑内核代码的时间
# - id :★ CPU 完全空闲、无事可做
# - wa :★★ iowait —— CPU 空闲,但【在等 IO 完成】
# - hi/si :处理硬/软中断

# === ★ iowait 到底是什么 ===
# iowait 不是"CPU 在干 IO 的活",CPU 不干 IO 的活。
# iowait 的准确含义是:★ 这段时间 CPU 其实【闲着】,
#   它没有别的进程可跑,而唯一在等的那些进程,
#   全都【卡在等磁盘 IO 上】—— CPU 只能干等。
# ★ 一句话:wa 高 = CPU 闲得发慌,但被磁盘拖住了。

# === ★ 于是"load 28、CPU 却闲"就讲通了 ===
# - CPU 视角:67% 时间是 wa(干等磁盘)+ 28% id ——
#   CPU 确实没在忙,idle 加 wa 占了 95%。
# - load 视角:那些被磁盘拖住的进程全是 D 状态,
#   全被算进 load,于是 load 被顶到 28。
# ★ 同一台机器,CPU 说"我闲",load 说"我忙"——
#   它俩说的根本不是一回事。CPU 说的是自己,
#   load 说的是"有多少进程在排队/被卡住"。

# === wa 高,要分清是"磁盘慢"还是"IO 太多" ===
# - 磁盘本身慢(老盘、坏盘、网络盘抖动)
# - 或者 IO 请求量太大,磁盘扛不住
# ★ 两种都会让 wa 飙高,下一步要去看磁盘本身的指标。

# === 快速确认 wa 是不是持续高 ===
$ vmstat 1 5
procs ---memory--- ---swap-- -----io---- --system-- ----cpu----
 r  b   ...                   bi    bo            us sy id wa
 1 24   ...                 8200  4100            3  2 28 67
#    ^^ ★ b 列 = 24,阻塞(等 IO)的进程数;wa = 67
# ★ vmstat 的 b 列和 wa 列一起高 = 实锤 IO 瓶颈。

修复 3:揪出是谁在狂读写磁盘

# === ★ 确认是 IO 瓶颈后,核心是找到"谁在狂读写" ===

# === 第一步:看磁盘整体被压得多狠 ===
$ iostat -x 1 3
Device  r/s   w/s   rkB/s   wkB/s  await  %util
vda     120   860   5400   42000   85.0   99.7
# 逐项看:
#  - r/s w/s     :每秒读/写次数
#  - rkB/s wkB/s :每秒读/写的数据量
#  - ★ await    :IO 请求的平均等待耗时(ms),越高越糟
#  - ★ %util    :磁盘繁忙度。接近 100% = 磁盘【满负荷】
# ★ %util 99.7 + await 85ms = 磁盘已经被打满、且很慢。

# === ★ 第二步:用 iotop 找出狂读写磁盘的进程 ===
$ iotop -oP
# - -o:只显示真正在产生 IO 的进程(o = only)
# - -P:按进程显示(不是线程)
#   TID  PRIO  USER  DISK READ  DISK WRITE  COMMAND
#  9300  be/4  app      0 B/s    38 M/s    java ...    # ★ 元凶
# ★ DISK WRITE 那列排第一的,就是在狂写磁盘的进程。

# === 没装 iotop 时,用 pidstat 看每个进程的 IO ===
$ pidstat -d 1 3
   UID    PID    kB_rd/s   kB_wr/s   Command
   1000  9300       0.0   38912.0   java         # ★ 每秒写 38M
# ★ pidstat -d:专看进程级的磁盘读写速率。

# === 看那个进程到底在读写哪些文件 ===
$ ls -l /proc/9300/fd | grep -vE 'socket|pipe'
# 看它打开的磁盘文件,结合业务,判断是在写日志、
#   写临时文件、还是数据库在刷盘。

# === ★ 第三步:判断是"突发"还是"一直如此" ===
$ iostat -x 5         # 持续看几轮
# - IO 一直很高 = 业务/配置问题(比如日志疯狂打、
#   某个批处理在跑、缓存失效全打到磁盘)
# - IO 偶尔尖刺 = 某个定时任务/突发流量
# ★ 弄清是常态还是突发,才能对症。

# === 也看一眼:是不是在疯狂用 swap(swap 也走磁盘)===
$ vmstat 1 3
 ... si  so ...
     0   0                # si/so 如果很大 = 在狂换 swap,
#                           那也会把磁盘 IO 打满 -> 其实是内存不够。

修复 4:D 状态进程——卡在不可中断睡眠

# === ★ load 高的"隐形推手":D 状态进程 ===

# === 进程状态速览 ===
# ps 的 STAT 列,常见这几个:
#  - R :运行 / 可运行(在 CPU 上,或排队等 CPU)
#  - S :可中断睡眠(在等事件,比如等网络、等锁,正常)
#  - ★ D :不可中断睡眠 —— 几乎都是在【等磁盘 IO】
#  - Z :僵尸
#  - T :被暂停

# === ★ D 状态特殊在哪 ===
# D = Uninterruptible Sleep。它在等一件【不能被打断】
#   的事 —— 典型就是"磁盘把这块数据读/写完"。
# ★ 这期间它:
#  - 连 kill -9 都【杀不掉】(信号要等它醒来才处理,
#    但它在 D 状态根本不处理信号)
#  - ★ 照样被算进 load average
# 所以一堆 D 进程,就是一堆"杀不掉、又顶着 load"的进程。

# === 把当前所有 D 状态进程揪出来 ===
$ ps -eo stat,pid,ppid,comm | awk '$1 ~ /^D/'
D    9301  9300  java
D    9302  9300  java
...
# ★ 一眼看清:24 个 D 进程,全是同一个服务(9300)的。
#   它们全卡在等磁盘上 —— 这就是 load 被顶到 28 的真相。

# === 看一个 D 进程到底卡在哪个内核调用上 ===
$ cat /proc/9301/stack
[<0>] ... wait_on_page_bit ...        # ★ 卡在等一个内存页(等磁盘)
[<0>] ...
# ★ /proc/PID/stack 显示进程当前卡在哪个内核函数 ——
#   出现 io/wait/page 之类的字样,基本实锤在等磁盘。

$ cat /proc/9301/wchan       # 更简短:就一个函数名
# 也能看它"睡在"哪个内核函数上。

# === ★ 重要提醒:D 进程多,别想着 kill ===
# D 状态进程 kill -9 也杀不掉 —— 它要等 IO 结束才醒。
# ★ 正确做法不是杀进程,是【解决磁盘 IO 瓶颈】——
#   IO 一通畅,D 进程自己就醒了,load 自己就掉下来。

# === 持续盯 D 进程数,验证修复有没有效 ===
$ watch -n2 "ps -eo stat | grep -c '^D'"
# ★ D 进程数往下掉,就说明 IO 瓶颈在缓解。

修复 5:怎么缓解磁盘 IO 瓶颈

# === ★ 找到狂读写磁盘的进程后,怎么治 ===

# === 方向 1:先看那些 IO 是不是"本不该有的" ===
# 很多 IO 瓶颈,根子是【不必要的磁盘写】:
#  - 日志级别开太低(DEBUG),疯狂打日志
#  - 每条请求都同步写一次磁盘文件
#  - 缓存没配好/失效,请求全打到磁盘或数据库
# ★ 先问:这些 IO 有没有必要?能不能【从源头减少】?
$ ls -lh /opt/app/logs/                  # 看日志是不是涨得离谱

# === 方向 2:把"同步写"改成"批量/异步写" ===
# 让程序攒一批数据再一次性落盘,而不是来一条写一次。
# ★ 这是开发侧的事,但运维能定位到"就是它在狂写",
#   把证据(iotop/pidstat 输出)甩给开发。

# === ★ 方向 3:给狂吃 IO 的进程降 IO 优先级 ===
# 如果是某个【非关键】任务(备份、批处理、日志压缩)
#   在抢 IO,把它的 IO 优先级降下来,别影响主业务:
$ ionice -c3 -p 9300                     # 把进程 9300 设为 IO 空闲级
# -c3 = idle,只在磁盘没人用时才让它读写。
# 启动时就降:
$ ionice -c3 nice -n19 ./batch_job.sh

# === 方向 4:确认不是内存不足导致的"假 IO 问题" ===
$ free -h
# ★ 如果内存不够,系统会疯狂用 swap,而 swap 走磁盘 ——
#   表面是 IO 瓶颈,根子是【内存不够】。这种要加内存,
#   而不是优化磁盘。vmstat 的 si/so 持续不为 0 就是信号。

# === 方向 5:磁盘本身的问题 ===
# - 机械盘换 SSD,IO 能力天差地别
# - 云盘检查是不是买的 IOPS 规格太低
# - dmesg 看有没有磁盘报错(坏道会让 IO 极慢)
$ dmesg -T | grep -i -E 'i/o error|ata.*error'

# === ★ 方向 6:监控要盯 IO 指标,不能只盯 CPU 和 load ===
$ iostat -x 1 | awk '/vda/{print $NF}'   # 持续取 %util
# ★ 把磁盘 %util、await 纳入监控告警 ——
#   只盯 CPU 利用率,IO 瓶颈会完全漏过去。

# === 治理思路 ===
# 短期:降非关键任务的 IO 优先级,先把主业务让出来。
# 长期:从源头减少不必要的写 + 改异步批量 + 该升盘升盘,
#   监控把 IO 指标补上。

修复 6:负载与 IO 排查纪律

# === 这次事故暴露的认知盲区,定几条纪律 ===

# === 1. ★ load average 不是 CPU 利用率 ===
# load 数的是"在跑 + 等 CPU + 等磁盘(D 状态)"的进程数。

# === 2. ★ 看到 load 高,第一件事是看 CPU 忙不忙 ===
$ top    # us+sy 高 = CPU 过载;id/wa 高 = 不是 CPU 的事

# === 3. ★ wa(iowait)高 = 瓶颈在磁盘 IO,不在 CPU ===
# iowait = CPU 闲着,但被等磁盘的进程拖住,走不下去。

# === 4. 确认 IO 瓶颈:iostat 看 %util 和 await ===
$ iostat -x 1 3        # %util 接近 100 = 磁盘满负荷

# === 5. ★ 找狂读写磁盘的进程:iotop / pidstat -d ===
$ iotop -oP        # 或  pidstat -d 1

# === 6. D 状态进程 kill 不掉,别杀,去解决 IO ===
$ ps -eo stat,pid,comm | awk '$1~/^D/'

# === 7. ★ 别把"内存不足导致狂换 swap"误判成 IO 问题 ===
$ vmstat 1     # si/so 持续不为 0 = 其实是内存不够

# === 8. 排查"load 高"的命令链 ===
$ uptime                       # ① 看 load 多高
$ top                          # ② 看 CPU:us+sy 高?还是 wa 高?
$ iostat -x 1 3                # ③ wa 高就看磁盘 %util
$ iotop -oP                    # ④ 找狂读写磁盘的进程
$ ps -eo stat,pid,comm|grep ^D # ⑤ 看 D 进程印证
# 按这个顺序,load 高基本能定位到底是 CPU 还是 IO。

命令速查

需求                        命令
=============================================================
看 load average             uptime  或  top 第一行
看 CPU 各项(含 wa)        top  看 %Cpu(s) 那行
看磁盘繁忙度和等待           iostat -x 1 3  (看 %util / await)
找狂读写磁盘的进程           iotop -oP
看进程级磁盘读写速率         pidstat -d 1
看可运行+阻塞进程数          vmstat 1  (r 列 / b 列)
揪出所有 D 状态进程          ps -eo stat,pid,comm | awk '$1~/^D/'
看 D 进程卡在哪个内核调用    cat /proc/PID/stack
给进程降 IO 优先级           ionice -c3 -p PID
看磁盘有没有硬件报错         dmesg -T | grep -i 'i/o error'

口诀:load 高先看 CPU,us+sy 高才是 CPU 过载,wa 高是磁盘 IO 在拖
      D 状态进程算进 load 又 kill 不掉,治 load 要去治磁盘 IO

避坑清单

  1. load average 不是 CPU 利用率,它统计在跑加排队等 CPU 加 D 状态等磁盘的进程数
  2. load 高有两种成因,CPU 不够或磁盘 IO 不够,光看 load 一个数分不清是哪种
  3. 看到 load 高第一件事是看 top 的 CPU 行,us 加 sy 高才是 CPU 过载
  4. iowait 也就是 wa 高,意思是 CPU 闲着但被等磁盘的进程拖住,瓶颈在磁盘不在 CPU
  5. 确认磁盘 IO 瓶颈用 iostat -x 看 %util 接近 100 和 await 等待耗时高
  6. 找狂读写磁盘的进程用 iotop -oP 或 pidstat -d,按 DISK WRITE 排序
  7. D 状态是不可中断睡眠几乎都在等磁盘,它连 kill -9 都杀不掉但照样算进 load
  8. D 进程多别想着杀,杀不掉也没用,要去解决磁盘 IO,IO 通畅它自己就醒
  9. 内存不足会狂换 swap 而 swap 走磁盘,表面像 IO 问题根子是内存,看 vmstat 的 si so
  10. 监控只盯 CPU 利用率会漏掉 IO 瓶颈,必须把磁盘 %util 和 await 也纳入告警

总结

这次"load average 高达 28、CPU 却闲着 90%"的事故,纠正了我一个用了很多年、却从来没认真推敲过的等式:load average = CPU 有多忙。这个等式在我脑子里根深蒂固到什么程度?根深蒂固到我看到 load 是 28、机器是 4 核的那一瞬间,我的下一步行动已经自动决定了——去找那个把 CPU 吃满的进程。我甚至没想过要先确认一下"CPU 到底忙不忙",因为在我的认知里,load 高就【是】CPU 忙,这是同一件事,不需要再确认。正因为这个等式被我当成了公理,当 top 那一行 CPU 统计明明白白告诉我"空闲 90% 多"时,我的大脑里产生了一个无法调和的冲突:一个写着"忙到爆"的指标,和一个写着"闲得很"的指标,同时出现在同一台机器上。我反复刷新、反复确认,不是因为我怀疑数字,而是因为在我那个"load 即 CPU"的世界观里,这种情况【根本不应该存在】。复盘到根上,我才真正读懂了 load average 这个数字。它从来就不是在回答"CPU 有多忙"这个问题。它回答的是另一个、更宽的问题:此时此刻,这个系统里,有多少个进程正处在"需要被推进、但还推不下去"的状态。这里面当然包括正在 CPU 上跑的、和排队等着上 CPU 的——这部分,确实和 CPU 忙不忙有关,也正是我唯一知道的那部分。但它还包括了一类我从来没纳入视野的进程:卡在不可中断睡眠、也就是 D 状态的进程。而这些 D 状态的进程,绝大多数,都在做同一件事——伸长了脖子,干等着磁盘把一块数据读完或写完。我那台机器的真相是:CPU 一点都不忙,它甚至闲得发慌,因为它根本无事可做;可它又走不开,因为唯一在等它的那些进程,全都被磁盘死死拖住了。磁盘被打满了,二十多个进程排着队卡在 D 状态,它们一个个都被算进了 load,把那个数字顶到了 28。CPU 和 load 这两个指标,从头到尾说的就不是一回事:CPU 利用率在描述"CPU 这个部件本身的繁忙程度",而 load average 在描述"整个系统里有多少工作被积压、被卡住"。一台机器完全可以 CPU 闲到打盹、同时积压如山——只要那座山,压的不是 CPU,而是磁盘。这次最大的收获,是我对"一个指标到底在度量什么"这件事,多了一份不敢想当然的审慎。一个指标的名字,常常会暗示一个比它真实含义更窄、或者干脆就是错位的解释。load average 里的 "load",太容易让人联想到"CPU 的负担"了,以至于我从没想过它度量的其实是"整个系统的积压"。指标是用来描述系统的,但指标本身不会向你解释它自己——它只给你一个数字,那个数字背后到底统计了什么、没统计什么,需要我自己去把它彻底搞清楚。下一次,当两个指标给出看似矛盾的结论时,我不会再以为是机器疯了——我会意识到,大概率是我把其中某一个指标的含义,理解窄了、或者理解偏了。矛盾不在机器那边,矛盾在我对这两个数字的解释里。先去把"这个数到底在数什么"问清楚,那个所谓的矛盾,往往就自己消失了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

服务进程凭空消失日志却干净:一次 Linux OOM Killer 排查复盘

2026-5-20 20:48:16

Linux教程

一行 fstab 写错整台服务器开不了机:一次 Linux 开机挂载排查复盘

2026-5-20 20:55:37

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索