负载飙到 50 而 CPU 却空闲:一次 Linux 系统负载排查复盘

4 核机 load 飙到 50,top 却显示 CPU 大半空闲。排查梳理:load average 三个数字与该除以核数、load 为何统计 R+D 两类进程、top 的 us/sy/id/wa/st 精读、用 vmstat/iostat 分清 CPU 型与 IO 型、pidstat 揪出打 IO 的元凶进程,以及一套负载排查纪律。

2024 年监控告警:一台 4 核的服务器,负载(load average)飙到了 50。我心里咯噔一下——load 50,这是 CPU 被榨干了十几倍啊。我火速 ssh 上去,敲 top,准备抓那个吃满 CPU 的进程。可 top 一打开,我反而更懵了:CPU 那一行,id(空闲)居然有百分之七八十,根本没几个进程在烧 CPU。负载 50,CPU 却大半空闲——这两件事摆在一起,完全是自相矛盾的。我对着这个矛盾盯了很久,才后知后觉地意识到:我从一开始,就把 load average 这个数字的含义,理解错了。这件事逼着我把 Linux 的负载、top、CPU 与 IO 排查这一整套彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,4 核服务器,跑着一个写日志很重的服务
事故现象:
- 监控告警:load average 飙到 50(4 核机)
- top 一看:CPU 的 id(空闲)有 70%~80%
- 负载极高 + CPU 大量空闲 —— 看起来自相矛盾

现场排查:
# 1. 看负载
$ uptime
 load average: 49.82, 47.31, 40.05
# —— 1/5/15 分钟负载都在 40~50,4 核机,严重过载

# 2. 看 CPU 到底忙不忙
$ top
%Cpu(s):  8.1 us,  3.2 sy,  0.0 ni, 76.5 id, 12.0 wa, ...
#                              ★ id 76.5% 空闲
#                              ★ wa 12.0% —— 这个 wa 不对劲

# 3. 看进程状态分布
$ ps -eo stat | grep -c '^D'
46                          # ★ 46 个 D 状态进程!
$ ps -eo stat | grep -c '^R'
3                           # 真正在跑(抢 CPU)的只有 3 个

# 4. 看是不是磁盘 IO 的问题
$ iostat -x 1 3
Device   %util   await
sda      99.7%   320ms        # ★ sda 利用率 99.7%,await 320ms
# —— 磁盘被打满了,每个 IO 请求平均要等 320 毫秒

根因(后来想清楚的):
1. 我一直以为 load average 衡量的是【CPU 有多忙】。
   错。load average 统计的是【不可立即推进的进程数】——
   它既包含正在抢 CPU 的进程(R 状态),
   也包含【卡在 IO 上等待的进程】(D 状态)。
2. 这台机的真实情况:CPU 几乎是闲的(id 76%),
   但有 46 个进程全部卡在 D 状态,在排队等那块
   被打满的磁盘返回数据。
3. 这 46 个"等 IO 的进程",被 load average 全部计入,
   于是负载冲到了 50。
4. 真正的瓶颈,从来不是 CPU,是【磁盘 IO】。
   服务疯狂写日志,把 sda 的 IO 能力打满了,
   后面的进程只能排长队等。
负载高 + CPU 闲 = 经典的 IO 瓶颈信号,不是 CPU 问题。

修复 1:看负载的工具与 load average 三个数字

# === 看 load average 的几个命令 ===
$ uptime
 11:20:33 up 30 days,  load average: 2.15, 1.80, 1.42
$ w | head -1                  # 第一行也有 load average
$ cat /proc/loadavg            # 最原始的来源
2.15 1.80 1.42 3/512 28931
# 前三个 = 1/5/15 分钟负载;3/512 = 可运行进程/总进程;最后是最新 PID

# === ★ 三个数字:1 分钟、5 分钟、15 分钟的平均负载 ===
# 它们一起看,能看出负载的【趋势】:
# 1分钟 > 5分钟 > 15分钟  -> 负载在【上升】,问题正在恶化
# 1分钟 < 5分钟 < 15分钟  -> 负载在【回落】,高峰已过
# 三个数字差不多        -> 负载平稳

# === ★ 负载多高才算"高"?要除以 CPU 核数 ===
$ nproc                        # 看有几个 CPU 核
4
# 经验法则:load average 和核数相比 ——
#   load ≈ 核数      每个核都满负荷,刚好用满,还算健康
#   load < 核数      有富余
#   load > 核数      过载,有进程在排队等不到资源
#   load = 核数×2 以上   明显过载,要排查
# ★ 一台 4 核机 load 4 很正常;一台 64 核机 load 4 是很闲。
#   脱离核数谈负载高低,没有意义。

# === 这次:4 核机 load 50 ===
# 50 / 4 ≈ 12.5 倍 —— 严重过载,必须排查。
# 但"过载"不等于"CPU 过载",下一节是这次的关键。

修复 2:load average 到底统计了什么——这次的根因

# === ★ 颠覆认知的一点:load 不只是"CPU 有多忙" ===
# 很多人(包括当初的我)以为:
#   load average = CPU 使用率的另一种表达
# 这是【错的】。

# === load average 的真实定义 ===
# Linux 的 load average,统计的是处于以下两种状态的进程数:
#   1. R 状态:正在 CPU 上运行,或在运行队列里等 CPU
#   2. D 状态:不可中断睡眠 —— 【通常是在等磁盘/网络 IO】
# 把这两类进程数,做一段时间的指数移动平均,就是 load average。

# === 所以 load 高,有两种【完全不同】的原因 ===
# 原因 A:R 状态进程多 —— 真的是 CPU 不够用(CPU 密集)
# 原因 B:D 状态进程多 —— 进程都在等 IO(IO 瓶颈)
# ★ 这两种原因,处理方式南辕北辙 ——
#   A 要加 CPU / 优化算法;B 要换快盘 / 减少 IO。
#   把 B 当成 A 来处理,加再多 CPU 也没用。

# === 一眼分辨是哪种:看进程状态分布 ===
$ ps -eo stat | sed 's/[^A-Z].*//' | sort | uniq -c | sort -rn
     46 D          # ★ D 这么多 -> 原因 B,IO 瓶颈
      3 R          # R 没几个 -> 不是 CPU 的锅
    180 S

# === 再一个铁证:CPU 空闲 + 负载高 = IO 瓶颈 ===
# top 里 id(空闲)很高,load 却很高 ——
# CPU 明明有空,进程却推进不下去,那它们在等什么?
# 答案几乎总是:IO。
# ★ "负载高但 CPU 闲" 这八个字,直接指向 IO,
#   这是这次事故教给我的、最值钱的一条判断。

# === 反过来:CPU 跑满 + 负载高 = 真 CPU 瓶颈 ===
# top 里 id 接近 0、us 很高,load 又高 —— 那才是 CPU 不够用。

修复 3:top 的 CPU 各字段精读

# === top 里 %Cpu(s) 那一行,每个字段都是线索 ===
$ top
%Cpu(s): 8.1 us, 3.2 sy, 0.0 ni, 76.5 id, 12.0 wa, 0.0 hi, 0.2 si, 0.0 st

# us (user)    用户态 CPU:跑应用代码用掉的。高 = 应用在算东西
# sy (system)  内核态 CPU:系统调用、内核用掉的。高 = 频繁陷内核
# ni (nice)    跑被 nice 调过优先级的进程用掉的
# id (idle)    ★ 空闲。这个数高 = CPU 没事干
# wa (iowait)  ★ CPU 空闲、且在等 IO 的时间占比。
#              ★ 这次 wa 12% —— wa 明显偏高,直指 IO 在拖后腿
# hi/si        硬中断/软中断耗时。si 高常见于网络包风暴
# st (steal)   ★ 虚拟机里:CPU 时间被宿主机/别的 VM "偷走"了
#              云主机上 st 高 = 你被邻居挤了,或被限流了

# === 怎么根据这几个数判断方向 ===
# us 高、id 低           -> 应用 CPU 密集,排查哪个进程在算
# sy 高                  -> 系统调用太频繁,可能是 fork/IO 系统调用多
# wa 高、id 也不低        -> ★ IO 瓶颈(本次)
# st 高                  -> 云主机被超卖/限流,联系云厂商
# id 高、但 load 还高     -> 进程在等 IO(D 状态),还是看 IO

# === top 里看进程,几个常用操作 ===
# 进入 top 后:
#   P    按 CPU 使用率排序(默认)
#   M    按内存使用率排序
#   1    展开看【每个 CPU 核】各自的使用率
#   x    高亮当前排序列
#   按 c  显示进程的完整命令行
# === 看单个进程的状态 ===
$ top -p 6500                  # 只盯一个 PID

修复 4:定位是 CPU 型还是 IO 型——vmstat 与 iostat

# === vmstat:一屏看清 CPU / 内存 / IO / 进程 的全局 ===
$ vmstat 1 5                   # 每 1 秒采一次,共 5 次
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 3 46      0 102400  20480 512000    0    0  8192 96000 3200 8800  8  3 77 12
# ★ 重点看这几列:
# r  运行队列里的进程数(等 CPU 的)—— r 持续 > 核数 = CPU 瓶颈
# b  ★ 被阻塞的进程数(等 IO 的)—— 这次 b=46,IO 瓶颈实锤
# wa CPU 等 IO 的时间占比 —— 这次 12%,偏高
# bi/bo  每秒从块设备读入/写出的【块数】—— bo 96000 很大 = 在狂写盘
# si/so  swap 换入/换出 —— 不为 0 说明内存不够在用 swap(另一个坑)

# === iostat:专门看磁盘 IO 的细节 ===
$ iostat -x 1 3                # -x 显示扩展信息
Device  r/s   w/s   rkB/s    wkB/s   await  %util
sda     12    880   480.0   352000   320.0  99.7
# ★ 每个字段的意义:
# w/s      每秒写请求数 —— 880,写得非常频繁
# wkB/s    每秒写入的 KB —— 352MB/s,接近这块盘的极限
# await    ★ 每个 IO 请求平均耗时(毫秒)—— 320ms 极高!
#          健康的机械盘 await 个位数到几十,SSD 更低。
#          await 几百毫秒 = 磁盘已经严重排队,撑不住了
# %util    ★ 磁盘繁忙时间占比 —— 99.7% = 这块盘几乎一刻不停在忙
#          %util 接近 100% = 磁盘【打满】了

# === 一句话判别 ===
# vmstat 的 r 高、wa 低           -> CPU 瓶颈
# vmstat 的 b 高、wa 高、iostat %util 满 -> IO 瓶颈(本次)
# vmstat 的 si/so 不为 0          -> 内存不足,在拿 swap 顶,IO 雪上加霜

修复 5:揪出制造 IO 的元凶进程

# === 确认了是 IO 瓶颈,下一步:是【谁】在狂打 IO ===

# === pidstat:按进程看 IO(最实用)===
$ pidstat -d 1 5               # -d 看磁盘 IO,每秒一次
   UID    PID   kB_rd/s   kB_wr/s  Command
     0   6500      0.0  340000.0  myapp           # ★ 元凶!
     0   8120      0.0      12.0  rsyslogd
# kB_wr/s 那一列,myapp 每秒写 340MB —— 就是它在打满磁盘。

# === iotop:像 top 一样,实时按 IO 排序进程 ===
$ iotop -oP
# -o 只显示真正有 IO 的进程,-P 按进程(不按线程)聚合
# 一眼能看到哪个进程的 DISK WRITE 最高。

# === 看某个进程具体在读写哪些文件 ===
$ ls -l /proc/6500/fd          # 它打开了哪些文件描述符
$ lsof -p 6500                 # 同上,信息更全
# —— 这次一看,myapp 开着一个 /data/log/debug.log,
#    定位到:有人把日志级别开成了 DEBUG,日志狂刷。

# === 看进程当前卡在哪个系统调用上 ===
$ cat /proc/6500/stack         # 内核态调用栈
$ strace -p 6500 -f -e trace=write -c   # 统计它在调哪些系统调用
# 能看到它是不是卡在 write() 上。

# === 这次的处置 ===
# 1. 立刻把 myapp 的日志级别从 DEBUG 调回 WARN —— IO 量骤降。
# 2. load average 几分钟内从 50 回落到 4 以下。
# 3. 复盘:DEBUG 日志不该在生产长期开;日志该异步写、该轮转。

# === 顺带:网络 IO 也可能是瓶颈来源 ===
$ pidstat -n 1 3               # 按进程看网络收发
$ iftop                        # 实时看网络流量
# 排查时别忘了 IO 不只是磁盘,也包括网络。

修复 6:负载排查纪律

# === 这次事故暴露的排查惯性,定几条纪律 ===

# === 1. ★ 第一纪律:load 高 ≠ CPU 高,先分清类型 ===
# 看到 load 高,别条件反射地去抓 CPU 进程。
# 先看 top 的 id 和 wa、看 ps 的 D 状态数:
#   id 低、R 多   -> CPU 型,去抓吃 CPU 的进程
#   id 高、wa 高、D 多 -> IO 型,去抓打 IO 的进程

# === 2. 评估负载高低,永远先除以核数 ===
$ nproc
# load 8 在 4 核机是过载,在 16 核机是悠闲。脱离核数没意义。

# === 3. 三个负载数字看趋势 ===
# 1分钟 > 15分钟 = 正在恶化,优先处理;反之是高峰已过。

# === 4. 一套固定的排查命令链 ===
$ uptime                       # ① 负载多高、趋势如何
$ top                          # ② id/wa/us 看 CPU 是哪种忙
$ vmstat 1 5                    # ③ r 还是 b 高,si/so 有没有
$ iostat -x 1 3                 # ④ 是 IO 型就看哪块盘 %util 满
$ pidstat -d 1 5                # ⑤ 揪出打 IO 的进程
# 按这个顺序走,基本不会跑偏。

# === 5. IO 型问题,处理方向 ===
# - 减少 IO:关掉 DEBUG 日志、日志异步化、加缓存
# - 加快 IO:机械盘换 SSD/NVMe、上 RAID
# - 分摊 IO:把高 IO 的目录挪到独立的盘
# ★ 给 IO 瓶颈加 CPU,一点用都没有 —— 这是这次最大的教训。

# === 6. 内存不足会伪装成 IO 问题 ===
$ free -h
$ vmstat 1 3                    # si/so 不为 0 = 在用 swap
# 内存不够 -> 频繁 swap -> 大量磁盘 IO -> load 飙高。
# 看到 IO 高,顺手确认一下是不是内存先不够了。

# === 7. 把这套指标接进监控,别等告警才查 ===
# load average、CPU 各分量(尤其 wa)、磁盘 %util 和 await,
# 都该是监控面板上的常驻项。

命令速查

需求                        命令
=============================================================
看 load average             uptime / w / cat /proc/loadavg
看有几个 CPU 核              nproc
看 CPU 各分量(id/wa/us)    top
看进程状态分布(R/D)        ps -eo stat | sort | uniq -c
全局看 CPU/内存/IO          vmstat 1 5
看磁盘 IO 细节(%util)      iostat -x 1 3
按进程看磁盘 IO             pidstat -d 1 5
实时按 IO 排序进程          iotop -oP
看进程在读写哪些文件        lsof -p PID
看是否在用 swap             free -h / vmstat(si/so)

口诀:load 高先 ÷ 核数 -> top 看 id 和 wa 分类型
      id 高 wa 高就是 IO 型 -> iostat + pidstat 揪元凶进程

避坑清单

  1. load average 不只统计 CPU,它包含 R(等CPU)和 D(等IO)两类进程
  2. 负载高但 CPU 大量空闲,是经典的 IO 瓶颈信号,不是 CPU 问题
  3. 评估负载高低必须先除以 CPU 核数,脱离核数谈高低没有意义
  4. 三个负载数字看趋势:1分钟大于15分钟说明负载正在恶化
  5. top 的 wa(iowait)偏高直接指向 IO 在拖累系统
  6. top 的 st(steal)高说明云主机 CPU 被宿主机或邻居抢占
  7. vmstat 的 r 高是 CPU 瓶颈,b 高是 IO 瓶颈,要分清
  8. iostat 的 %util 接近 100% 说明磁盘已打满,await 几百毫秒是严重排队
  9. 用 pidstat -d 或 iotop 揪出真正在打 IO 的元凶进程
  10. 给 IO 瓶颈加 CPU 毫无作用,内存不足引发 swap 也会伪装成 IO 问题

总结

这次"负载 50、CPU 却空闲"的事故,纠正了我一个埋藏得极深、自己却从未察觉的错误认知:我一直以为,load average 这个数字,就是 CPU 繁忙程度的一种表达方式——负载高,就等于 CPU 被榨干了。正是这个根深蒂固的等式,让我在 ssh 上去的第一时间就直奔 top,一心想抓那个吃满 CPU 的进程;也正是这个等式,让我在看到 top 显示 CPU 大半空闲时,陷入了长久的困惑——因为在我的认知框架里,"负载 50"和"CPU 空闲"这两件事,根本不可能同时成立。复盘到根上,我才终于搞懂了 load average 真正统计的是什么。它衡量的,根本不是"CPU 有多忙",而是一个更宽泛的东西:在某一段时间里,平均有多少个进程,处在"想往前推进、却推进不了"的状态。而一个进程推进不下去,会有两种截然不同的原因。第一种,是它正在抢 CPU,或者在运行队列里排队等着上 CPU——这就是 R 状态的进程,这一类,确实和 CPU 的繁忙程度直接挂钩。但还有第二种,也是这次我完全忽略了的:进程已经不需要 CPU 了,它发起了一个磁盘读写请求,然后就陷入了"不可中断睡眠",也就是 D 状态,死死地等着磁盘把数据返回给它。load average 的真相在于——它把 R 和 D 这两类进程,【一视同仁地】全都数了进去。所以,一个高企的负载数字背后,可能是 CPU 真的不够用了(R 状态进程一大片),也可能是另一幅完全不同的图景:CPU 其实闲得很,但有一大批进程,正排着长队、苦苦地等待一块不堪重负的磁盘——而这,恰恰就是我那台服务器当时真实的样子。它上面那个写日志极重的服务,把磁盘的 IO 能力彻底打满了,iostat 显示那块盘的 %util 高达 99.7%,每个 IO 请求平均要排队等上 320 毫秒;于是四十多个进程齐刷刷地卡在 D 状态,被 load average 尽数计入,负载这才冲到了 50。CPU 在这整个过程里,自始至终都是清白的、空闲的。这次事故让我刻进脑子里的,是一条极其简洁、却极其有用的判断:当你看到"负载很高,但 CPU 却大量空闲"这两个现象同时出现时,几乎可以不假思索地下结论——这是 IO 瓶颈,而不是 CPU 瓶颈。因为道理很朴素:CPU 明明有大把空闲算力,进程却依然推进不下去,那它们一定是在等待 CPU 之外的某种资源,而这种资源,绝大多数时候就是磁盘 IO(有时是网络 IO)。想通了这一层,排查的方向才算彻底拨正:接下来该做的,根本不是去 top 里抓 CPU 大户,而是用 iostat 去看哪一块磁盘的 %util 被打满了,再用 pidstat -d 去揪出究竟是哪一个进程在疯狂地读写它。这一步我做下去,答案立刻就浮出了水面:是那个服务的日志级别被人不小心开成了 DEBUG,导致它每秒往磁盘上倾泻几百兆的日志。我把日志级别调回 WARN,IO 量应声而落,负载在几分钟之内,就从 50 平稳地回落到了 4 以下。这次事故最让我后怕的一点是:如果我没有搞懂 load average 的真相,我极有可能会得出"这台机器 CPU 不够用了"的错误结论,然后去申请给它扩容、加 CPU 核数——而那将是一笔完全打了水漂的投入,因为对一个 IO 瓶颈的系统来说,加再多的 CPU,也丝毫改变不了那块磁盘排着长队的事实。这次从一个自相矛盾的监控数字出发,我最大的收获,是终于戒掉了"看到 load 高就去抓 CPU"这个危险的条件反射,换上了一个朴素而正确的排查起点:看到负载高,先别急着动手,先用 top 看一眼 CPU 到底是真忙还是假忙,先分清楚我面对的,究竟是一个 CPU 问题,还是一个 IO 问题——方向对了,排查才有意义。

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

几百个僵尸塞满进程表:一次 Linux 进程信号排查复盘

2026-5-20 18:28:31

Linux教程

核心服务凌晨被处决:一次 Linux 内存与 OOM Killer 排查复盘

2026-5-20 18:36:00

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