·约 8 分钟阅读·DeepSeek

读懂 Linux 负载平均值,以及它为什么会骗你

Linux 负载平均值统计的是可运行任务与不可中断任务,而非 CPU 使用率。本文讲清 1/5/15 分钟三个数字真正在度量什么、为什么 iowait 与 D 状态进程会把它抬高、为什么要按核数归一化、容器 CPU 配额如何打破旧规则,以及该改用 vmstat、mpstat、PSI、iostat 看什么。

linuxdevops

每次有人 ping 我「服务器好慢,负载 40,要挂了」,我打开 shell 敲的第一个命令都不是 top,而是 vmstat。因为运维里最容易被误读的一个数字就是 Linux 负载平均值,而它 落进脑子里的位置,恰好和「CPU 很忙」是同一个抽屉。这两者不是一回事,而它们之间的差 距,就是凌晨三点做出各种糟糕决策的地方。

这篇是简短而诚实的版本:负载到底在数什么,为什么 CPU 什么都没干它也会涨,为什么先要除 以核数再决定慌不慌,为什么容器悄悄把老规则弄失效了,以及我改看什么。

Linux 负载平均值到底是什么

uptime 给出的三个数字,是可运行任务数与不可中断任务数的指数移动平均。不是百分比, 不是速率,而是每个采样点上「想跑」或「卡在不可中断睡眠(D 状态,通常阻塞在 I/O)」 的任务数量。

$ uptime
 03:14:07 up 41 days,  2:13,  3 users,  load average: 6.28, 4.91, 3.02

$ cat /proc/loadavg
6.28 4.91 3.02 3/812 40219

前三个字段是 1、5、15 分钟的平均值。3/812 表示当前有 3 个可运行任务、总共 812 个任务, 40219 是最近分配出去的 PID。内核每五秒采样一次(更早是每个 tick 一次),把每个采样折 进三条时间常数不同的 EWMA 序列。整个机制就是这样:一个作用在任务计数上的低通滤波器。

这个定义会直接导出两件极其重要的事。第一,它是一个计数,所以含义取决于你有多少颗 CPU。第二,它把根本没有在用 CPU 的任务也算了进去。下面分别说。

为什么它不等于 CPU%:iowait 与 D 状态进程

最容易把人绊倒的地方在这:一个阻塞在慢盘上的任务,是会被算进负载的。如果 MySQL 在等一 块快坏的 SSD,或者备份正在猛敲一块机械盘,这些线程会停在 D 状态——不可中断睡眠,在 I/O 返回之前连信号都杀不掉——而每一个都在给负载加一。这段时间里 CPU 全程空闲。负载在 涨,iowait 在涨,实际什么计算都没发生。

这就是为什么「负载高、CPU 闲」是 Linux 上最常见也最容易被误读的模式之一。iowait 高, 意味着你有一个存储问题,却伪装成了 CPU 问题。top 会给你看 %wa 字段,值得知道的是 %wa 自身的度量方式有点奇怪(本质是 CPU 在 idle-wait 上走的 tick),但信号是真实的: 调度器手里有可运行的工作,可存储层就是完不成。

$ 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
 0  6      0 812304  91024 2401888    0    0  3120  9408  512  980  3  1 12 84  0
 1  5      0 808920  91024 2401000    0    0  4088  8752  488  940  4  1 10 85  0

看 b 列:5~6 个任务阻塞在 D 状态,wa(iowait)大约 84%。r 接近零——没人想要 CPU。 这里的负载平均值会被这些阻塞任务顶得很高,但机器根本不是 CPU 瓶颈,而是磁盘瓶颈。去追 磁盘,别追 CPU。

按核数归一化

负载是任务计数,CPU 是干活的工人数。真正带信息的是那个比值。在 4 核机器上,负载 8 意味着 每核排了两个任务——真的满负荷了。在 64 核机器上,负载 8 意味着大多数核心都在摸鱼。同一个 绝对数字,在一台机器上是危机,在另一台上是风平浪静的下午。

我用的经验法则:把负载除以逻辑核数。低于约 0.7,可以放松。接近 1.0,满载但跟得上。 持续高于 1.0,队列在形成,延迟在爬升。远高于核数,你确实遇到问题了——但动手前先看下面 「哪种问题」那一节,因为它可能是 D 状态,而不是 CPU。

$ nproc
8
$ uptime | awk -F'load average:' '{print $2}'
 6.28, 4.91, 3.02
# 6.28 / 8 ≈ 0.79 每核——很忙,但没淹死

为什么负载平均值在现代系统上会误导人

这个公式几十年没变过,可它跑在的系统变了。

核越来越多。 一台 128 核的机器吞下负载 100 可以毫无感觉。「负载该低于 CPU 数量」这句 经典建议,是在 4 核已经算多的年代写下的。

cgroup 与容器配额。 这是最大的一个坑。在一个通过 cpu.max 限制到 2 核的容器里,从 /proc/loadavg 读到的负载平均值反映的是宿主机,不是你的 cgroup。你的容器可能负载 0.4、宿主机 60;反过来,你的容器被限流到爬行,宿主机负载看着却很平静。CPU 配额限流活在 cpu.stat(nr_throttled、throttled_usec)里,从来不在负载平均值里。如果你用 Kubernetes 配了 CPU limit,这才是要紧的数字,而负载永远不会显示它。

$ cat /sys/fs/cgroup/cpu.max          # cgroup v2,容器配额
200000 100000                          # 每 100ms 周期 2 颗 CPU

$ grep throttled /sys/fs/cgroup/cpu.stat
nr_throttled 10482
throttled_usec 8120344112              # 约 2.25 CPU 小时被配额偷走

再谈 D 状态膨胀。 在一群共用同一个吵闹存储后端的容器上,每个容器的负载都可能被同一块 磁盘抬高。你会得到一条横跨整个集群的「负载高」告警,点名 40 个 pod,而它们全是同一个 iowait 源头的受害者。

那该看什么

负载平均值是一个没有特异性的症状。去找那些能点名资源的工具:

  • vmstat 1 ——要紧的列是 r(运行队列)、b(阻塞/D 态)、wa(iowait)、 si/so(swap)。它用一行就把「CPU 想要」和「I/O 阻塞」分开。
  • mpstat -P ALL 1 ——逐核看,用来在一个平均值看着正常时抓出某颗单独烧起来的 CPU。
  • iostat -xz 1 ——设备上的 %util 与 await,也就是 b 列从哪来的。
  • /proc/pressure ——压力失速信息(PSI),现代而诚实的答案。它报告的是「部分/全部 任务在 CPU、内存或 I/O 上失速」所占的墙上时钟比例——最接近「这到底把我坑得多惨」的度量。
$ cat /proc/pressure/io
some avg10=41.20 avg60=38.90 avg300=12.30 total=981230112
full avg10=22.10 avg60=19.80 avg300=6.05 total=502113900

some 表示至少有任务在 I/O 上失速;full 表示每一个可运行任务都在失速。full 到 20%+ 就意味着这台机器的活儿真在等磁盘。这比一个「负载 40」有用得多。

把这套习惯和监控一支小舰队里的做法配起来——对你能 采取行动的症状告警,而不是对你无能为力的计数告警。

一个简短的实际例子

一台机器在 8 核上报出负载 40。慌张的说法是「超载五倍」。诊断只要三条命令。

$ uptime
 09:02:11 up 12 days,  load average: 40.10, 33.80, 12.05
$ vmstat 1 3
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0 37      0 120400  40120  980000    0    0 12040 14000  600 1200  2  1  5 92  0
 0 38      0 118900  40120  979000    0    0 11800 13900  580 1180  2  1  4 93  0
$ cat /proc/pressure/io
some avg10=55.10 avg60=48.20 avg300=15.00 total=482112309
full avg10=39.90 avg60=33.40 avg300=10.00 total=210044100

b 是 37,r 是 0,wa 是 92%。几乎全部的「负载」都是 37 个卡在 D 状态等 I/O 的任务, PSI 也确认机器有近一半墙上时间是卡在同一块磁盘上。要修的是存储——一块退化中的盘、一个失控 的写入者,或者一个被打满的后端卷——不是 CPU。如果你靠「加 CPU」或「缩小容器」去『修』它, 只会更糟。同一套纪律也出现在嵌入式系统上调试 TCP: 先在真正阻塞的那一层做测量,再去调你能看见的那一层。

「负载 40,到底坏不坏?」——三种快速判读

单看数字毫无意义,是上下文把它变成决策。三个具体场景:

64 核上负载 40,CPU 100%,r 约 40,PSI cpu some 60%。 这是真的 CPU 饱和。运行队列 又长又满,而不是阻塞。扩出去、找出热循环,或者接受这份延迟——但这确实是 CPU 问题。

64 核上负载 40,CPU 只用了 10%,b 约 38,PSI io full 35%。 根本不是 CPU 问题。你有 约 38 个任务阻塞在一块慢盘上。去修存储路径,加 CPU 什么都不会发生。

2 核容器上负载 40,宿主机负载 55,cpu.stat 显示大量限流。 容器是被自己的配额饿着, 不是被 CPU。这里的负载是宿主机的产物;该拿去提工单的数字是 nr_throttled。

8 核上负载 40,CPU 90%,r 为 8,1 分钟均值在回落。 一次已经在消退的突发。等一等。 不是每一次尖峰都需要 runbook。

共同点:负载平均值只告诉你「有东西在排队」,从不告诉你「是什么」在排队。 第一个问题永远 是「排的是 CPU、是 I/O,还是配额?」只有 vmstat、PSI 和逐核视图能把它们分开,也只有到那 时候,「40」才真正有意义。