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