- Published on
为什么平均负载不是 CPU 使用率 — load average 24 而 CPU 只有 30% 的时候
- Authors

- Name
- Youngju Kim
- @fjvbn20031
开篇 — load average 24,可 CPU 只有 30%
凌晨收到告警。一台 8 核服务器的平均负载超过了 24。登录上去打开 top,CPU 空闲还剩 60% 以上。这时很容易滑向两个结论之一:“监控出问题了”,或者“得把 CPU 扩到三倍”。两个都是错的。
平均负载不是 CPU 使用率。连单位都不一样。使用率是比例 (0~100%),而负载是个数 (任务数量)。而且 Linux 在数这个个数时,多数了一样其他 Unix 不数的东西。就是这一样东西占了本文的一半篇幅。
uptime
# 03:41:52 up 12 days, 3:21, 2 users, load average: 24.31, 22.08, 14.95
nproc
# 8
cat /proc/loadavg
# 24.31 22.08 14.95 2/1284 48210
看最后一行的 2/1284。前面的 2 是此刻可运行的线程数,1284 是线程总数。负载是 24,可可运行的线程只有 2 个。剩下的 22 在哪里呢?这个问题的答案就是诊断的起点。
三个数字的真身 — 5 秒采样与指数移动平均
内核按 LOAD_FREQ 周期,也就是每 5 秒一次 (准确地说是 5 秒加 1 个 tick,用意是避开与周期性中断的相位重合),从每个 CPU 的运行队列采样活跃任务数。活跃任务是这样计算的。
nr_active = rq->nr_running + rq->nr_uninterruptible
nr_running 是当前正在运行或可运行的任务,nr_uninterruptible 是处于不可中断睡眠状态的任务。把这个和在所有 CPU 上相加,就是那一瞬间的样本。
接下来的更新公式才是核心。内核用定点运算计算下面这些。
load = (load * EXP + active * (FIXED_1 - EXP)) >> FSHIFT
FSHIFT = 11, FIXED_1 = 2048
EXP_1 = 1884 (2048 * e^(-5/60))
EXP_5 = 2014 (2048 * e^(-5/300))
EXP_15 = 2037 (2048 * e^(-5/900))
把常数验算一遍,理解会快很多。每 5 秒更新一次而要得到 1 分钟的时间常数,衰减系数必须是 e 的负 5/60 次方,也就是 0.9200。0.9200 乘以 2048 等于 1884。5 分钟和 15 分钟按同样方式算出 2014 和 2037。
由此引出一个在实战中很重要的性质。这不是算术平均,而是指数衰减平均。负载像台阶一样跃升时,1 分钟平均恰好在 1 分钟后达到最终值的 63.2%,要达到 99% 则需要大约 5 分钟。按同样的逻辑,15 分钟平均会被拖上一个多小时。
| 经过时间 | 1 分钟平均反映率 | 5 分钟平均反映率 | 15 分钟平均反映率 |
|---|---|---|---|
| 1 分钟 | 63.2% | 18.1% | 6.5% |
| 5 分钟 | 99.3% | 63.2% | 28.3% |
| 15 分钟 | 100.0% | 95.0% | 63.2% |
| 45 分钟 | 100.0% | 100.0% | 95.0% |
所以“1 分钟平均大于 5 分钟和 15 分钟就说明负载正在上升”这个惯用解读是成立的。反过来,故障刚结束时只有 15 分钟平均还很高,属于正常现象。用那个数字设告警,恢复之后还会响 20 分钟。
Linux 独有的关键差异 — 连 D 状态也算进去
传统 Unix 把平均负载只定义为运行队列的长度,也就是等待 CPU 的任务数。Linux 在 1993 年往里面加上了 TASK_UNINTERRUPTIBLE 状态。意图很明确:不只是衡量 CPU 需求,而是要衡量对整个系统资源的需求。
TASK_UNINTERRUPTIBLE 就是在 ps 中显示为 D 的状态。它是连信号都唤不醒的内核内部等待,典型的有这些。
- 等待块设备 I/O 完成 (
io_schedule) - 等待页缓存锁 (
folio_wait_bit_common) - 等待硬挂载的 NFS 服务器响应
- 等待某些内核互斥量和信号量
- 等待设备驱动的固件响应
这份清单里没有 CPU。也就是说,即便 CPU 完全闲着,平均负载照样可以涨到任意高度。只要有一台 NFS 服务器不响应,访问过那个挂载点的进程就会全部堆积在 D 状态,8 核服务器的负载冲到 200,而 CPU 曲线一片平坦。这是正常行为。
有一个例外值得记住。从内核 4.2 开始引入了 TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD),无事可做而等待的内核线程不再计入负载。在 ps 中显示为 I 的那些就属于这一类。
ps -eo state,pid,comm --no-headers | awk '{c[$1]++} END {for (s in c) print c[s], s}' | sort -rn
# 1183 S
# 68 I
# 22 D
# 2 R
# 9 Z
D 是 22,R 是 2。和前面看到的负载 24 严丝合缝。到这一步结论只有一个。这台服务器不是 CPU 不够,而是在某个地方等待 I/O。
这里顺带点一下常见的错误说法。“平均负载就是 CPU 等待队列的长度”这句话在其他 Unix 上成立,在 Linux 上是错的。而“负载低于核心数就安全”这条规则,只要那个数值里混着 D 状态,就同样保证不了安全。
“按核心数相除”在哪里失效
把负载除以 nproc,小于 1.0 算宽裕、大于 1.0 算饱和,这个习惯作为起点还算好用。但在下面五种情况下会失效。
第一,D 状态污染。和上一节完全一样。24/8 = 3.0,可这和 CPU 饱和毫无关系。
第二,平均值的滞后。30 秒的尖峰在 1 分钟平均里连一半都体现不出来。反过来,早已结束的故障却会一直留在 15 分钟平均里。
第三,SMT(超线程)。nproc 数的是逻辑 CPU。8 个物理核心开启 SMT 会显示 16,但实际处理能力并不是 16 核。负载 12 不等于“16 核里用了 75%”。
lscpu | grep -E '^CPU\(s\)|Thread|Core|Socket'
# CPU(s): 16
# Thread(s) per core: 2
# Core(s) per socket: 8
# Socket(s): 1
第四,cgroup 配额。容器上设了 CPU limit 而被限流的任务会彻底从运行队列里退出,因此根本不会被平均负载统计到。在 64 核的宿主机上,一个被限制到 0.5 CPU 的 Pod 慢得离谱,宿主机负载却安安静静。这个组合后面还会再讲。
第五,归属信息的缺失。负载是全系统范围内的一个标量。它不会告诉你是哪个容器、哪个服务、哪种资源导致的。你可以拿它设告警,但那个告警能让你做的事只有“进去看看”。
负载高而 CPU 空闲的情况 — 诊断路径
这里是有顺序的。从上到下三分钟就能走完。
# 第 1 步: 确认负载的构成 — R 少而 D 多就是 I/O 路径
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
# 2 22 0 512344 84120 6231044 0 0 0 0 2103 4102 4 3 21 72 0
# 1 23 0 511208 84120 6231180 0 0 132096 412 2288 4390 3 4 19 74 0
# 2 22 0 510992 84120 6231212 0 0 128512 380 2251 4301 4 3 20 73 0
b 列是 22 到 23。这一列正是处于不可中断睡眠的任务数。wa (iowait) 达到 72% 说的也是同一件事。iowait 是“CPU 空闲但该 CPU 上存在 I/O 等待任务的时间”,所以它其实是空闲时间的一种。数值高本身并不等于坏,但和 b 列一起看,方向就很明确了。
# 第 2 步: 看看是哪个设备饱和了
iostat -x 1 3
# Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s w_await aqu-sz %util
# nvme0n1 1842.0 58944.0 0.0 0.00 12.85 32.00 210.0 3360.0 3.02 24.30 99.60
# nvme1n1 2.0 64.0 0.0 0.00 0.31 32.00 1.0 12.0 0.22 0.00 1.20
要读的有三样。%util 是不是贴在了 100%(设备一刻不得闲)、r_await/w_await 是平时的多少倍 (每个请求的延迟)、aqu-sz 有多深 (队列里积压的平均请求数)。在 NVMe 上,%util 100% 可能因为并行处理而并不意味着饱和,所以判断要靠 await 和队列深度。
# 第 3 步: 逐个查看 D 状态的进程
ps -eo state,pid,ppid,comm --no-headers | awk '$1 == "D"' | head
# D 4821 4102 postgres
# D 4822 4102 postgres
# D 7710 1 kworker/u33:2
# 第 4 步: 看那个进程停在内核的什么位置 (需要 root 权限)
sudo cat /proc/4821/stack
# [<0>] io_schedule+0x46/0x80
# [<0>] folio_wait_bit_common+0x131/0x330
# [<0>] filemap_fault+0x5f0/0xa50
# [<0>] __do_fault+0x39/0x120
# [<0>] handle_mm_fault+0xd4e/0x1090
栈顶是 io_schedule 就是块 I/O,看到 rpc_wait_bit_killable 或 nfs_wait_on_request 就是 NFS,属于 __lock_page 一类的则是页缓存争用。如果 /proc/PID/stack 是空的或者报权限错误,那要么是内核没有 CONFIG_STACKTRACE,要么是权限不足。
怀疑 NFS 时,直接看挂载统计。
# 查看每台服务器的 RTT 与重传
nfsstat -c | head -20
mountstats --nfs /mnt/shared 2>/dev/null | grep -A3 'READ:'
# 找出没有响应的服务器 (硬挂载会无限等待)
grep nfs /proc/mounts
# 10.0.9.4:/export/data /mnt/shared nfs4 rw,hard,proto=tcp,timeo=600,retrans=2 0 0
hard 挂载会把进程按在 D 状态,直到服务器回来为止。这种状态下连 kill -9 都不管用。从运维角度可以考虑 softerr 或更短的 timeo,但要先确认数据一致性方面的要求。
负载低却依然很慢的情况 — 负载看不见的东西
反方向也很常见。负载是 0.8,p99 却是 3 秒。这时把负载在结构上根本看不见的东西列出来,候选范围就缩小了。
- 可中断等待(S 状态)不计入负载。等待套接字响应的时间,也就是下游 API 或数据库的延迟,全都属于这里。应用变慢的原因大多在这一侧。
- 用户态锁争用也大多处于 S 状态。futex 等待是可中断的,所以再严重负载也不会上升。
- cgroup CPU 限流如前所述,因为把任务从运行队列里摘掉了,所以看不见。
- 单线程瓶颈按定义就是负载 1。哪怕有 64 个核心,负载也不会超过 1。
- 虚拟机监控器的窃取时间意味着 CPU 被抢走了,而不是队列变长了。
区分这五种情况的命令如下。
# S 状态下在等什么 (套接字等待在这里看不到,所以要看栈)
sudo cat /proc/9931/stack | head -5
# 锁争用: 上下文切换是否暴涨
vmstat 1 3 # cs 列是平时的多少倍
pidstat -w -p 9931 1 3
# UID PID cswch/s nvcswch/s Command
# 1000 9931 18422.0 34110.0 java
# 限流 (cgroup v2)
cat /sys/fs/cgroup/cpu.stat
# nr_periods 86400
# nr_throttled 41287
# throttled_usec 2914837261
# steal
mpstat -P ALL 1 3 | tail -3
nvcswch/s (非自愿上下文切换) 大就是 CPU 争用,cswch/s (自愿) 大则是因为锁或 I/O 等待而睡下又醒来的模式。
PSI — 回答平均负载答不上来的那个问题的指标
平均负载的根本问题在于,它只说有多少个在等待,却不说停顿了多久。从内核 4.20 进来的 PSI 量的正是后者。
cat /proc/pressure/io
# some avg10=78.23 avg60=71.04 avg300=48.92 total=8172635241
# full avg10=61.17 avg60=55.83 avg300=37.10 total=6019283746
cat /proc/pressure/cpu
# some avg10=2.41 avg60=1.88 avg300=1.02 total=91827364
# full avg10=0.00 avg60=0.00 avg300=0.00 total=0
cat /proc/pressure/memory
# some avg10=0.00 avg60=0.00 avg300=0.00 total=142
# full avg10=0.00 avg60=0.00 avg300=0.00 total=88
读法是这样的。
some是至少有一个任务因为该资源而停顿的时间占比。full是所有可运行任务同时停顿的时间占比。也就是说,在那段时间里系统没能做成任何有用的事。avg10、avg60、avg300分别是 10 秒、60 秒、300 秒窗口的百分比,total是累计的微秒数。- 全系统
cpu的full按定义没有意义,所以恒为 0 (从内核 5.13 开始这一行本身会被输出)。
上面的输出说明的事情很清楚。I/O 的 full 是 61%,意思是最近 10 秒里有 6 秒以上整个系统都因为 I/O 而停顿。这比负载 24 这个数字直接得多,更重要的是它可以直接换算成损失掉的吞吐量。
PSI 的第二个优点是它按 cgroup 粒度存在。这正是平均负载所没有的归属信息。
# 哪个 cgroup 因为 I/O 停顿得最久
for f in /sys/fs/cgroup/**/io.pressure; do
v=$(awk '/^full/ {print $2}' "$f" | cut -d= -f2)
printf '%6s %s\n' "$v" "${f%/io.pressure}"
done 2>/dev/null | sort -rn | head -5
# 58.12 /sys/fs/cgroup/system.slice/postgresql.service
# 3.04 /sys/fs/cgroup/system.slice/containerd.service
# 0.00 /sys/fs/cgroup/user.slice
如果没有 /proc/pressure,那要么是 CONFIG_PSI 被关掉了,要么是内核以 CONFIG_PSI_DEFAULT_DISABLED=y 构建,需要启动参数 psi=1。
| 指标 | 量的是什么 | 是否反映 D 状态 | 时间窗口 | cgroup 粒度 |
|---|---|---|---|---|
| load average | 正在等待的任务个数 | 包含 | 1/5/15 分钟 EMA | 不支持 |
| CPU 使用率 | 真正用在 CPU 上的时间比例 | 不包含 | 任意 | 支持 |
| iowait | 空闲 CPU 中存在 I/O 等待的部分 | 间接 | 任意 | 不支持 |
| PSI cpu.some | 因为 CPU 而停顿的时间 | 不包含 | 10/60/300 秒 | 支持 |
| PSI io.full | 因为 I/O 整体停顿的时间 | 事实上就是它 | 10/60/300 秒 | 支持 |
结语 — 平均负载是队列长度,不是使用率
只记住一件事的话,就记这个。Linux 的平均负载是把等待 CPU 的任务和等待 I/O 的任务不加区分地合并计数后的指数移动平均。所以光凭这一个数字,回答不了“要不要加 CPU”。
整理成实战规则就是下面这些。
- 负载高的时候,先看
/proc/loadavg的第四个字段和vmstat 1的b列。R 与 D 的比例决定方向。 - 如果 D 很多,就往
iostat -x的 await 和/proc/PID/stack下钻。加 CPU 不是答案。 - 告警设在 PSI 上,而不是负载上。
cpu.some avg60和io.full avg60越过阈值,与真实延迟的相关性更强,误报也少得多。 - 负载可以留在仪表盘上,但只当趋势指标用。1 分钟值大于 5 分钟和 15 分钟就说明在上升,读到这里为止,不多读。
- 如果是容器环境,宿主机负载解释不了 Pod 的性能。必须去看 cgroup 的
cpu.stat和*.pressure。