负载与 CPU 判读:load、top、vmstat
load average 是被误读最多的指标:它高不代表 CPU 忙,它低也不代表系统健康。本章讲清 load 的构成(Linux 把不可中断的 D 状态也算进去)、top 各列的含义、vmstat 的读法,以及“load 高但 CPU 不高”这类经典问题的排查路线。
load average 到底是什么
uptime 输出的三个数字是 1 分钟、5 分钟、15 分钟的平均“可运行 + 不可中断”进程数。
load 的组成 = R(可运行,等 CPU)+ D(不可中断睡眠,通常在等 IO)
注意:Linux 的 load 包含 D 状态;传统 UNIX 只算 R 状态,所以两边的 load 不可直接比较
| 现象 | 含义 | 说明 |
|---|---|---|
| 1 分钟 > 15 分钟 | 负载正在上升 | 刚发生的变化,优先看最近变更与流量 |
| 1 分钟 < 15 分钟 | 负载正在回落 | 可能是瞬时尖峰,也可能是扩容起了作用 |
| 三者接近且都高 | 持续过载 | 容量真的不够,需要扩容或优化 |
| 三者都低但延迟高 | 不是负载问题 | 去查锁、网络、下游依赖与 GC |
load 要和核数一起看
| 核数 | load 值 | 判断 |
|---|---|---|
| 4 | 1 | 大约用掉 25% 的处理能力,健康 |
| 4 | 4 | 队列已满,请求开始排队,延迟抬头 |
| 4 | 8 | 平均每个核有 2 个任务在等,延迟翻倍级恶化 |
| 4 | 20+ | 严重过载,通常伴随超时与雪崩 |
经验阈值:load 持续高于核数的 1.5 倍就该介入,高于 3 倍必须止血。注意容器场景下 load 是宿主机级别的,容器内看到的 load 可能与本容器无关,此时应改用 cgroup 的 CPU 限流指标(cpu.stat 里的 nr_throttled)判断。
load 高但 CPU 不高:先找 D 状态
这是最经典的分叉点,答案几乎总在等待 IO 或等待锁。
# 谁在 D 状态(不可中断睡眠)
ps -eo state,pid,ppid,comm,wchan:24 | awk '$1=="D"'
# 确认是否 IO 等待
vmstat 1 5
iostat -x 1 3
pidstat -d 1 3
| load 高 + CPU 表现 | 结论 | 下一步 |
|---|---|---|
| CPU 也高(us+sy 接近核数×100%) | 计算型过载 | top -Hp <pid> 找热点线程,再上火焰图 |
CPU 低,%wa 高 | 磁盘或网络存储慢 | iostat -x 1 看 %util 与 await |
CPU 低,%wa 也低,D 进程多 | 等锁、等 NFS、等内核态资源 | strace -cp <pid>、cat /proc/<pid>/wchan |
CPU 低,%si 高 | 软中断处理不过来 | 查网卡流量、丢包与小包攻击 |
| 容器内 load 高、宿主正常 | cgroup 被限流 | 看 cpu.stat 的 nr_throttled 与 throttled_time |
top 各列怎么读
| 列 | 含义 | 判读要点 |
|---|---|---|
us | 用户态 CPU 占比 | 高说明业务代码在算,去看火焰图 |
sy | 内核态 CPU 占比 | 高说明 syscall、锁争用、内存分配频繁 |
ni | 调整过优先级的用户态占比 | 一般很低,高说明有人在跑 nice 任务 |
id | 空闲占比 | 与 us+sy+wa 互补 |
wa | 等待 IO 占比 | 高即磁盘饱和,配合 iostat -x 确认 |
hi | 硬中断占比 | 高常见于网卡、磁盘中断过多 |
si | 软中断占比 | 高常见于高 PPS 流量或网络攻击 |
st | 被虚拟化偷走的 CPU | 云主机高说明宿主机超卖,只能联系云厂商 |
%CPU(进程区) | 单进程 CPU 占用 | 可能超过 100%,多线程进程在多核上总和不封顶 |
RES | 进程实际物理内存 | 排查内存时看这一列,不看 VIRT |
S | 进程状态 | R 运行、D 不可中断、S 睡眠、Z 僵尸 |
看到 %CPU 是 380%,说明这个进程同时用了约 4 个核,不代表数据错误。要定位到具体线程:
top -Hp <pid> # 线程级,SPID 即线程 ID
top -Hp <pid> -b -n 1 -o %CPU | head -25
printf '%x\n' <tid> # 转十六进制后去线程栈里搜 "nid=0x..."
vmstat 列判读
vmstat 1 第一行是开机以来的均值,从第二行起才是每秒的瞬时值,别拿第一行做判断。
| 列 | 含义 | 阈值与结论 |
|---|---|---|
r | 运行队列长度(等 CPU 的进程数) | 持续大于核数 → CPU 不够 |
b | 阻塞进程数(多为 D 状态) | 持续大于 0 → 有 IO 或锁阻塞 |
si / so | 每秒换入/换出内存(KB) | 非 0 且持续 → 内存不足,延迟必抖 |
bi / bo | 每秒块设备读入/写出(块) | 结合 iostat 判断是否磁盘打满 |
in | 每秒中断数 | 突增查网卡与磁盘中断 |
cs | 每秒上下文切换数 | 远高于平时 → 锁竞争或线程数过多 |
us / sy | 用户态/内核态占比 | sy 高查 syscall,us 高查代码 |
id / wa | 空闲/IO 等待占比 | wa 高即磁盘瓶颈 |
st | 被偷走的 CPU | 云主机上高说明邻居在抢占 |
上下文切换的经验值:几万次/秒在 8 核机器上正常;如果 cs 随 r 一起暴涨且 CPU 不高,多半是锁竞争或线程池开太大,而不是算力不够。
# 一次采集,同时留下 CPU、上下文切换、队列三组数
vmstat 1 10 > /tmp/vmstat.txt
pidstat -w 1 5 # 哪个进程在疯狂切换
mpstat -P ALL 1 3 # 是否只有个别核忙
处置动作
| 场景 | 立即动作 | 后续动作 |
|---|---|---|
| 计算型过载 | 限流、降级非核心接口、扩容 | 火焰图定位热点函数并优化 |
| IO 等待导致 load 高 | 减少写入(降日志级别、关埋点) | 换 SSD、拆分数据目录、优化大查询 |
| 锁竞争 | 回滚最近改动、临时降并发 | 缩小锁粒度、拆分热点 key |
| 软中断高 | 限流入口、加网卡多队列与 RPS | 排查异常小包流量 |
| 容器被限流 | 提 CPU limit 或加副本 | 调整 request/limit 比例 |
预防与巡检
- 把 load 按核数归一化成“每核负载”再设告警,避免不同规格机器共用一套阈值。
- 同时采集
us/sy/wa三项,只报 load 的告警无法区分计算型还是 IO 型过载。 - 记录每次发布前后的 load 基线,容量规划靠数据不靠感觉。
- 云主机与容器务必单独看
st与nr_throttled,否则会把平台问题当成自己的问题。
小结:load 是“排队任务数”而不是 CPU 使用率,Linux 的 load 还包含 D 状态;判断时先除以核数,再分叉到 CPU、IO、锁、软中断四条路径;top 的 %CPU 可以超过 100%,定位到线程后转十六进制查栈;vmstat 从第二行起读,重点看 r、b、si/so、cs 与 wa,si/so 一旦持续非 0,延迟问题就找到了主因。