CPU 不高 load 却爆表:一次 Linux 磁盘 IO 瓶颈排查复盘

一台 8 核服务器又慢又卡,top 看 CPU 几个核都闲着,load average 却高达 38,两个数字自相矛盾。排查梳理:load average 算的不只是等 CPU 的进程,还算上卡在 D 状态等磁盘 IO 的进程;iowait 是 CPU 在干等磁盘的证据;iostat -x 看 %util 与 await 量化磁盘有多忙;iotop 与 pidstat -d 揪出 IO 大户进程;/proc/PID/io 看累计落盘量;vmstat 看 si/so 排除内存不足引发的 swap IO,以及一套磁盘 IO 排查纪律。

2024 年,一台跑得好好的服务器,某天开始变得又慢又卡——上去敲个命令都要顿一下,服务响应肉眼可见地拖沓。我条件反射地敲了 top,准备抓那个吃满 CPU 的罪魁祸首。可 top 的画面让我愣住了:CPU 使用率不高,几个核都闲着,可那个 load average,赫然是 38——这台机器只有 8 个核。一边是悠闲的 CPU,一边是高得吓人的负载,这两个数字像是来自两台不同的机器,凑在一张屏幕上互相打脸。我一直以为 load average 高,就是 CPU 忙不过来了,可眼前的 CPU 分明没忙。这个矛盾逼着我去重新搞懂一件我自以为早就懂了的事:load average 这个数字,到底是用什么算出来的。这件事逼着我把 Linux 的负载、iowait、磁盘 IO 排查这一整套彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,8 核服务器,跑一个有大量读写的服务
事故现象:
- 服务器整体又慢又卡,敲命令都卡顿
- top 看 CPU 使用率不高,核都挺闲
- ★ load average 高达 38(机器才 8 核)
- CPU 闲 + load 爆表,两个数字自相矛盾

现场排查:
# 1. top 看一眼:CPU 闲,load 却爆表
$ top
load average: 38.5, 36.2, 30.1          # ★ 8 核机器,load 38
%Cpu(s): 5.0 us, 3.0 sy, 0.0 ni, 12.0 id, 80.0 wa
#                                              ^^^^^^^ ★ wa 80%!

# 2. ★ %wa(iowait)高达 80% —— 关键线索
# CPU 不是在干活,是在【干等磁盘 IO】

# 3. 看是不是有进程卡在 D 状态(不可中断睡眠)
$ ps -eo state,pid,comm | grep '^D'
D 9001 java
D 9005 java
D 9210 mysqld                            # ★ 一堆 D 状态进程
# ★ D 状态 = 进程在等 IO,卡死动不了

# 4. ★ iostat 看磁盘到底忙成什么样
$ iostat -x 1 3
Device  ...  r/s    w/s   await  %util
vda     ... 1200   3400   220.5  99.8     # ★ %util 99.8%!await 220ms!
# ★ 磁盘利用率打满 100%,单次 IO 平均要等 220 毫秒

根因(后来想清楚的):
1. ★ load average 算的不只是"等 CPU 的进程",
   它还算上了"等磁盘 IO 而卡在 D 状态的进程"。
2. ★ 所以 load 高有【两种】原因:CPU 不够用,
   或者 —— IO 太慢,一堆进程在排队等磁盘。
3. 这次是后者:磁盘 %util 打到 100%、await 220ms,
   磁盘已经是瓶颈,大量进程卡在 D 状态等它。
4. CPU 闲,是因为它无事可做 —— 它在 iowait,
   在干等那个慢吞吞的磁盘把数据吐出来。
5. ★ 我盯着 CPU 找凶手,方向全错:凶手是磁盘 IO。
load 高 + CPU 闲 + wa 高,几乎一定是磁盘 IO 瓶颈。

修复 1:load average 到底算了什么——不只是 CPU

# === ★ 先纠正最核心的误解:load average ≠ CPU 使用率 ===

# === load average 是什么 ===
$ uptime
... load average: 38.5, 36.2, 30.1
#                  1分钟  5分钟  15分钟
# load average = 系统在过去一段时间里,
#   平均有多少个进程【处于"想干活"的状态】。

# === ★ 关键:"想干活"包含【两种】进程 ===
# 1. 正在用 CPU、或在排队等 CPU 的进程(R 状态)
# 2. ★ 卡在【不可中断睡眠】等 IO 的进程(D 状态)
# ★ 第 2 种,正是大多数人(包括过去的我)漏掉的!
# 在 Linux 里,等磁盘 IO 等到卡死的进程,
#   也被算进 load —— 它们也是"系统的负担"。

# === ★ 于是 load 高,有两种完全不同的病 ===
# 病 A:CPU 不够用 -> 一堆进程在排队抢 CPU
#       特征:load 高,且 CPU 使用率(us+sy)也高
# 病 B:★ IO 太慢 -> 一堆进程卡在 D 状态等磁盘
#       特征:load 高,但 CPU 使用率低、iowait 高
# ★ 这次就是病 B。光看 load 数字,分不出是哪种病。

# === 怎么快速分辨是哪种病 ===
$ top
%Cpu(s): 5.0 us, 3.0 sy, ..., 12.0 id, 80.0 wa
# us+sy 高、wa 低  -> 病 A,CPU 瓶颈
# us+sy 低、wa 高  -> ★ 病 B,IO 瓶颈(这次)
# ★ load 是"有多少进程在等",至于在等 CPU 还是等磁盘,
#   要靠 us/sy/wa 这几个百分比来区分。

# === load 多高算高 ===
# 粗略基准:load 持续 > CPU 核数,就说明系统在过载。
# 8 核机器 load 38 -> 严重过载,平均每核背着近 5 个进程。

修复 2:iowait 与 D 状态——CPU 在"干等"的证据

# === ★ %wa(iowait):CPU 闲,但闲得"不情愿" ===
$ top
%Cpu(s): 5.0 us, 3.0 sy, 0.0 ni, 12.0 id, 80.0 wa, ...
# us  用户态 CPU      sy  内核态 CPU
# id  ★ 真正空闲(无事可做)
# wa  ★ iowait:CPU 没活干,但【是因为在等 IO 完成】
# ★ id 和 wa 都表示"CPU 没在算",但意义天差地别:
#   id 高 = 系统真闲;wa 高 = 系统被磁盘拖住了,
#   CPU 想干活却干不了,只能干等磁盘。

# === ★ iowait 高,本质是"磁盘喂不饱 CPU" ===
# 进程要读数据 -> 数据在磁盘上 -> 磁盘慢吞吞地找 ->
#   这期间进程只能睡着等 -> CPU 也跟着没活干 -> 计入 wa。
# wa 高 = 你的瓶颈在磁盘,不在 CPU。加 CPU 没用。

# === ★ D 状态进程:被 IO 卡死的进程 ===
$ ps -eo state,pid,ppid,comm | awk '$1=="D"'
D 9001 1 java
D 9210 1 mysqld
# 进程状态(state)的几个值:
#   R  运行中 / 可运行(在用或在等 CPU)
#   S  可中断睡眠(正常等事件,如等网络)
#   D  ★ 不可中断睡眠 —— 通常就是【在等磁盘 IO】
#   Z  僵尸进程
# ★ D 状态的进程,连 kill -9 都杀不动 —— 它在等内核
#   把 IO 做完,谁也打断不了。大量 D 状态 = IO 出大问题。

# === 持续盯 D 状态进程的数量 ===
$ watch -n1 "ps -eo state | grep -c '^D'"
# 这个数字一直很高 -> 系统持续被 IO 卡住。

# === ★ 把这次的链路串起来 ===
# 磁盘慢 -> 进程读写时卡在 D 状态 -> D 状态进程计入 load
#   -> load 飙高;同时 CPU 在等磁盘 -> iowait 飙高、
#   CPU 使用率却低。
# load 38 + CPU 闲 + wa 80%,讲的是【同一个】故事:磁盘。

修复 3:iostat——看磁盘到底忙不忙

# === ★ 确认是 IO 问题后,用 iostat 量化磁盘有多忙 ===
# iostat 来自 sysstat 包:yum install sysstat

# === 看磁盘的详细 IO 指标 ===
$ iostat -x 1 3
# -x 显示扩展指标  1 每秒刷新  3 共采 3 次
Device  r/s    w/s   rkB/s   wkB/s  await  r_await w_await %util
vda     1200   3400  48000   136000 220.5  180.2   235.1   99.8

# === ★ 这几个字段,每一个都是排查关键 ===
# r/s, w/s     每秒读 / 写多少次 IO(IOPS)
# rkB/s,wkB/s  每秒读 / 写多少 KB(吞吐量)
# ★ await     单次 IO 平均耗时(毫秒)——【从发起到完成】
#             普通 SSD 个位数 ms,机械盘十几 ms。
#             220ms = 磁盘已经被压垮,IO 在严重排队。
# ★ %util     磁盘繁忙度 —— 这块盘有多少时间在处理 IO。
#             接近 100% = 磁盘【满负荷】,这就是瓶颈。

# === ★ 怎么判读 ===
# %util 接近 100% + await 远高于正常 -> 磁盘是瓶颈,实锤。
# %util 不高               -> 磁盘没满,瓶颈在别处。
# (注意:%util 对 SSD/多队列盘会有点失真,要和 await 一起看)

# === 看每个磁盘分区/整体 IO 概览 ===
$ iostat                          # 不带 -x,看简要
$ iostat -x 2                     # 每 2 秒持续刷,观察趋势

# === ★ 还要看是不是"内存不够导致的 swap IO" ===
$ vmstat 1 5
procs ---memory--- ---swap-- ---io--- -system- ----cpu----
 r  b  ...          si   so   bi   bo  ...     us sy id wa
 2 35  ...          120  340  ...           ...      80
# ★ b 列 = 阻塞(等 IO)的进程数,这里 35,很高。
# ★ si/so = swap in/out,如果不为 0 -> 内存不足在
#   疯狂换页,这本身就会制造大量磁盘 IO!
# 排查 IO 高,别忘了回头看一眼是不是内存不足引起的。

修复 4:揪出是哪个进程在狂读写——iotop 与 pidstat

# === ★ 确认磁盘是瓶颈后,要找出"是谁在狂读写" ===

# === iotop:像 top 一样,但看的是 IO ===
$ iotop -o
# -o 只显示【真正在产生 IO】的进程
Total DISK READ: 45 M/s | Total DISK WRITE: 130 M/s
  TID  PRIO USER  DISK READ  DISK WRITE  COMMAND
 9210  be/4 mysql   2 M/s      88 M/s    mysqld          # ★ 写大户
 9001  be/4 app    40 M/s      5 M/s     java            # ★ 读大户
# ★ 一眼看出 mysqld 在狂写、java 在狂读 —— 锁定嫌疑进程。
# iotop 来自 iotop 包,需要 root 跑。

# === ★ pidstat:统计每个进程的 IO(不用装 iotop 时)===
$ pidstat -d 1 5
# -d 看磁盘 IO  1 每秒  5 次
   UID  PID    kB_rd/s   kB_wr/s  kB_ccwr/s  Command
   27   9210     2048     90112       0      mysqld
  1000  9001    40960      5120       0      java
# kB_rd/s 每秒读  kB_wr/s 每秒写 —— 和 iotop 信息类似。

# === 看某个具体进程的 IO 明细 ===
$ pidstat -d -p 9210 1
# 持续观察 mysqld(9210)的读写速率。

# === ★ 再往下:这个进程在读写【哪些文件】===
$ ls -l /proc/9210/fd                       # 它打开了哪些文件
$ cat /proc/9210/io                         # ★ 该进程累计 IO 统计
read_bytes:  10854203392
write_bytes: 88243507200                    # ★ 累计写了 88G
# /proc/PID/io 里 read_bytes / write_bytes 是
#   这个进程【实际落盘】的字节数,很有参考价值。

# === 用 strace 看进程具体在对哪个文件做 IO(谨慎用)===
$ strace -f -e trace=read,write -p 9210 2>&1 | head
# ★ strace 会拖慢目标进程,生产上短暂采样即可,别长期挂。

修复 5:定位到进程之后——往哪个方向治

# === ★ 找到"谁在狂 IO"之后,要分清是"正常的多"还是"异常" ===

# === 方向 1:是不是日志/调试输出在疯狂写盘 ===
# 经典坑:某次把日志级别开成了 DEBUG,
#   程序疯狂往磁盘写日志 -> 磁盘 IO 打满。
$ cat /proc/9001/io | grep write_bytes      # 看它写了多少
# 把日志级别调回 INFO/WARN,IO 立刻下来 —— 这种最常见。

# === 方向 2:数据库在大量刷盘 ===
# mysqld 狂写,常见于:大批量写入、大事务、
#   慢查询导致临时文件落盘、binlog/redo 刷盘频繁。
$ mysql -e "SHOW ENGINE INNODB STATUS\G" | grep -A5 'FILE I/O'
# ★ 治理思路:批量写改小批次、优化慢查询、
#   调整 innodb_flush_log_at_trx_commit 等(需评估)。

# === 方向 3:★ 是不是内存不足,在疯狂 swap ===
$ free -h
$ vmstat 1 3                                 # 看 si/so
# si/so 持续不为 0 -> 内存不够,系统在拿磁盘当内存用,
#   解法是加内存 / 降低内存占用,而不是治磁盘。

# === 方向 4:磁盘本身能力不够(硬件层)===
# 如果 IO 量是【合理的业务量】,只是磁盘扛不住:
# - 机械盘换 SSD,IOPS 和 await 差一个数量级
# - 云盘升级到更高 IOPS 规格
# - 把读写热点分散到多块盘

# === ★ 临时降低 IO 影响:ionice 给进程降优先级 ===
$ ionice -c3 -p 9001
# -c3 = idle 级别:只在磁盘空闲时才让这个进程做 IO,
#   适合给"不紧急的批处理/备份进程"降级,
#   把宝贵的 IO 让给在线服务。

# === 一句话 ===
# 找到 IO 大户后,先问"它该不该产生这么多 IO":
#   不该 -> 治程序(日志级别/批量/查询);
#   该   -> 治硬件(换盘/升配)或错峰(ionice)。

修复 6:磁盘 IO 排查纪律

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

# === 1. ★ load 高,先分清是 CPU 瓶颈还是 IO 瓶颈 ===
$ top
# us+sy 高 -> CPU 瓶颈;wa 高、CPU 闲 -> IO 瓶颈

# === 2. ★ load 算的是 R + D 状态进程,不止 CPU ===
# 等磁盘卡在 D 状态的进程,一样计入 load。

# === 3. iowait 高 = CPU 在干等磁盘,加 CPU 没用 ===
$ ps -eo state | grep -c '^D'        # D 状态进程多 = IO 卡

# === 4. ★ 用 iostat -x 量化磁盘 ===
$ iostat -x 1 3
# %util 接近 100% + await 远超正常 = 磁盘是瓶颈

# === 5. 用 iotop / pidstat -d 揪出 IO 大户进程 ===
$ iotop -o
$ pidstat -d 1

# === 6. ★ 别忘了排查内存:swap 也会制造大量 IO ===
$ vmstat 1 3                          # 看 si/so

# === 7. 排查 IO 问题的命令链 ===
$ uptime / top                        # ① load 和 wa,先定性
$ ps -eo state | grep -c '^D'         # ② D 状态进程多不多
$ iostat -x 1 3                       # ③ 磁盘 %util / await
$ iotop -o  /  pidstat -d 1           # ④ 谁在狂读写
$ cat /proc/PID/io                    # ⑤ 该进程累计 IO 量
$ vmstat 1 3                          # ⑥ 排除内存不足引发的 swap IO
# 按这个顺序,IO 瓶颈基本能定位。

命令速查

需求                        命令
=============================================================
看 load average             uptime  /  top
看 CPU 各项占比(含 wa)     top 看 %Cpu 行
数 D 状态(等 IO)进程      ps -eo state | grep -c '^D'
看磁盘详细 IO 指标          iostat -x 1 3
看哪个进程在狂读写          iotop -o
按进程统计 IO               pidstat -d 1
看某进程累计 IO 字节        cat /proc/PID/io
看阻塞进程数和 swap         vmstat 1 3
给进程的 IO 降优先级        ionice -c3 -p PID

口诀:load 高先看 top 的 wa,wa 高 CPU 闲就是磁盘 IO 瓶颈
      iostat 看 %util 打没打满、await 高不高,iotop 揪 IO 大户

避坑清单

  1. load average 不等于 CPU 使用率,它算的是 R 加 D 状态的进程数
  2. 等磁盘 IO 卡在 D 状态的进程也计入 load,所以 load 高未必是 CPU 忙
  3. load 高先看 top 的 %wa,wa 高且 CPU 使用率低就是磁盘 IO 瓶颈
  4. iowait 高说明 CPU 在干等磁盘,这种情况加 CPU 核数毫无帮助
  5. D 状态是不可中断睡眠,通常在等 IO,连 kill -9 都杀不动
  6. iostat -x 看 %util 接近 100% 加 await 远超正常,即磁盘是瓶颈
  7. iotop -o 或 pidstat -d 揪出在狂读写的进程,/proc/PID/io 看累计 IO
  8. 内存不足引发的 swap 会制造大量磁盘 IO,vmstat 看 si/so 别漏排
  9. 日志级别误开成 DEBUG 是 IO 打满的经典原因,先查日志输出量
  10. IO 量合理但磁盘扛不住就换 SSD 升配,不紧急进程可用 ionice 降级

总结

这次"CPU 明明很闲、load 却高到 38"的事故,纠正了我一个用了很多年的、几乎已经成为肌肉记忆的错误等式:load average 等于 CPU 的繁忙程度。在我过去的认知里,load 这个数字的含义是不言自明的——它就是衡量 CPU 累不累的那根指针。load 高,意思就是 CPU 忙不过来了,有一堆进程在排队等着抢 CPU;要解决,无非是优化掉那个吃 CPU 的大户,或者干脆加几个核。正是这个等式,让我在 top 的画面前彻底卡壳:一个高达 38 的 load,和几个明明闲着的 CPU 核,如果 load 真的只衡量 CPU,那这两个数字就是公然地、不可调和地互相矛盾。它们不可能同时为真——可它们偏偏就在同一块屏幕上,同时为真。复盘到根上,我才真正理解了 load average 这个数字的本义,而它的本义,比我以为的要宽得多。load 衡量的,从来不是"有多少进程在等 CPU",而是"有多少进程处于'想干活'的状态"。这是一个关键的、被我忽略了多年的区别。一个"想干活"的进程,确实可能是在等 CPU——它万事俱备,只差一个空闲的核心,这是我熟知的那一种。但它也可能是另一种我从未纳入考量的状态:它要读一块数据,而这块数据躺在慢吞吞的磁盘上,于是它发起了一个 IO 请求,然后就卡死在那里,动弹不得,连 kill -9 都打不断它——这就是所谓的 D 状态,不可中断的睡眠。这样一个卡在 D 状态苦等磁盘的进程,它没有占用一丝一毫的 CPU,可它同样是"系统的一个负担",同样是一个"想干活却干不成"的进程,因此,它一样被堂堂正正地计入了 load。这就是矛盾的解药:我那台机器上,真相不是 CPU 忙不过来,而是磁盘慢得离谱——%util 死死地贴在 100%,单次 IO 的 await 高达 220 毫秒。磁盘成了那个真正的瓶颈,大量的进程发起读写后,一个接一个地卡进了 D 状态,排着长队等待这块被压垮的磁盘。是这条长长的 D 状态队列,把 load 顶到了 38。而 CPU 之所以闲,恰恰是因为它无能为力——它不是没活干,是它要算的数据还在磁盘里没出来,它只能干等,这份"不情愿的空闲",就老老实实地体现在了 top 里那个高达 80% 的 iowait 上。load 38、CPU 闲、wa 80%,这三个我一度以为互相打架的数字,其实从头到尾都在异口同声地讲述同一个故事:磁盘扛不住了。这次从一个自相矛盾的 top 画面里走出来,我最大的收获,是把"load = CPU 忙"这个用了多年的错误等式,郑重地擦掉了。load 高,只是系统在喊"我背着很多想干活的进程",至于这些进程到底卡在哪里——是卡在 CPU 前,还是卡在磁盘前——load 这个数字本身,一个字都没说。要听清它们究竟卡在哪,得去看 iowait,去数 D 状态的进程,去用 iostat 量一量那块盘的 %util。一个高高的 load,是一个问题的开始,而绝不是一个答案。

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

能 ping 通服务却连不上:一次 Linux 网络分层排查复盘

2026-5-20 20:02:31

Linux教程

脚本手动跑没问题,放进 cron 就不执行:一次 Linux 定时任务排查复盘

2026-5-20 20:10:41

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