技术指南

Linux 负载很高但 CPU 不满:区分运行队列、D 状态与资源压力

从 load average 的 R/D 组成出发,用 PSI、任务状态、设备延迟与 cgroup 计数区分 CPU 竞争、不可中断等待和局部限流。

Linux 的 load average 不是 CPU 使用率。它统计一段时间内处于可运行状态(R)或不可中断睡眠状态(D)的任务;因此 CPU 仍有空闲而负载很高时,优先区分三件事:任务在等 CPU、任务卡在不可中断等待,还是某个 cgroup 被限流。先固定时间窗和任务状态,再决定减并发、修存储路径或调整资源边界。

先把高负载分到一条证据链

同窗观察优先解释下一步
vmstat 的 r 持续高,CPU PSI some 增长,CPU idle 很低可运行任务在竞争 CPU用 pidstat 找到持续消耗者,并核对并发、系统态和 VPS steal
vmstat 的 b 或 D 状态任务持续存在,IO PSI 增长任务在不可中断等待;存储只是候选原因之一保存 PID/TID、wchan、挂载与设备延迟,再对齐内核和服务日志
宿主 CPU 看似有余量,但目标 cgroup 的 nr_throttled/throttled_usec 增长服务受到自己的 CPU 带宽边界影响核对 cpu.max、父级 cgroup 与真实并发,不用整机平均掩盖局部限流
1 分钟负载已回落,当前 r/b 与 PSI 都正常历史尖峰已经结束回到尖峰时间窗的监控、调度、部署和业务日志,不用当前 ps 反推过去

保存一组同时间窗快照

vmstat 的 -y 会省略自开机以来的首份累计报告,后续五行是 1 秒采样;旧版 procps-ng 可能没有 -y 或 -w,应先查看本机 vmstat --help。PSI 不可用时记录为内核或配置差异。
date --utc --iso-8601=seconds
uptime
cat /proc/loadavg
for file in /proc/pressure/cpu /proc/pressure/io /proc/pressure/memory; do
  printf '\n[%s]\n' "$file"
  if test -r "$file"; then cat "$file"; else echo unavailable; fi
done
vmstat -y -w 1 6

`/proc/loadavg` 前三项是 1、5、15 分钟负载,第四项斜线前是当前可运行调度实体数。长窗口仍高而当前队列已空并不矛盾。load average 也不会按 CPU 数自动归一化;比较主机或容器前必须同时记录可用 CPU、亲和性和配额。

R 多时定位真正的 CPU 竞争

pidstat 需要 sysstat;按 1 秒间隔读取任务 CPU 与上下文切换,避免只看自开机累计值。ps 的 %CPU 是进程生命周期平均比例,适合定位候选而不是代替区间采样。
pidstat -u -w -p ALL 1 5
ps -eLo pid,tid,ppid,stat,psr,pcpu,wchan:32,comm --sort=-pcpu
cat /proc/stat | sed -n '1p'

CPU PSI 的 `some` 表示至少有任务因 CPU 资源而停顿;系统级 `full` 没有可用语义并保持为零,不能拿来判断整机 CPU 饱和。把 r、CPU PSI、pidstat 的 user/system 比例、业务吞吐和延迟放在同一窗口:高 r 但吞吐不增通常先处理并发、锁竞争或热循环,而不是只加线程。

VPS 还要查看 `/proc/stat` 或 vmstat 的 steal。内核把它定义为虚拟 CPU 被宿主用于其他虚拟处理器的非自愿等待;它能提示宿主调度影响,但单个尖峰不能证明长期超售。保留多个窗口并与应用延迟、供应商状态和迁移事件交叉验证。

D 多时先找等待位置与所有者

D 表示不可中断睡眠,wchan 是任务在内核中睡眠位置的符号线索;符号可能因权限或内核配置缺失。日志和命令输出可能包含设备、路径与租户信息,证据文件应最小授权并在分享前脱敏。
ps -eLo state,pid,tid,ppid,wchan:32,etimes,comm \
  | awk 'NR == 1 || $1 ~ /^D/'
pidstat -d -p ALL 1 5
journalctl --kernel --since='15 minutes ago' \
  --output=short-iso-precise --no-pager

不要把每个 D 状态都直接写成“磁盘坏了”。先看同一个 PID/TID 是否跨多个样本持续存在,再把 wchan、pidstat 的块 I/O 延迟、目标文件系统、远程挂载、swap-in、设备错误和服务日志对齐。短暂 D 是正常内核行为的一部分;持续堆积且伴随用户延迟才构成需要处置的证据。

设备指标只用于验证存储路径

iostat -y 省略自开机累计报告,-x 展示设备扩展统计,-z 隐藏无活动设备。保存设备映射、文件系统和虚拟化层;云卷、device-mapper、RAID 与容器看到的设备名可能不是最终物理设备。
iostat -xz -y 1 5

`await` 包含排队和服务时间,`aqu-sz` 是平均队列长度;必须结合请求率、读写方向、块大小与业务延迟解释。`%util` 接近 100% 对串行服务请求的设备可提示饱和,但 sysstat 明确指出 RAID 和现代 SSD 能并行服务请求,此值不能表示它们的性能上限。不要仅凭 `%iowait` 或 `%util` 宣布磁盘故障。

检查 cgroup 限流与 VPS steal

适用于 systemd 管理且使用统一 cgroup v2 的服务。把 example.service 换成真实 unit;同时检查父级 slice。读取不会改变配额,任何 cpu.max 调整都应通过 unit 或配置管理完成。
unit=example.service
cg=$(systemctl show "$unit" -p ControlGroup --value)
test -n "$cg"
for name in cpu.stat cpu.max cpu.pressure; do
  path="/sys/fs/cgroup${cg}/${name}"
  printf '\n[%s]\n' "$name"
  if test -r "$path"; then cat "$path"; else echo unavailable; fi
done

`cpu.max` 是 cgroup 的带宽上限;`cpu.stat` 在控制器启用时提供 `nr_periods`、`nr_throttled` 与 `throttled_usec`。这些值是累计计数,只有在相同业务窗口内的增量才能支持“正在被限流”的判断。若父级也限流,单改子 unit 无效;若没有限流增量,就回到 runnable、D 状态和宿主 steal,而不是继续放大配额。

只改变已经被证据指向的一层

根因优先变更回滚边界
无界 CPU 并发或热循环限制入口/worker,并修复循环、锁或算法恢复旧版本或旧并发值,保留队列而不是丢任务
持续 D 状态且设备延迟同步升高修复故障设备、远程挂载或 I/O 模式;先减写入恢复已知可用挂载/版本,停止会继续损坏数据的写入
目标 cgroup 持续限流按已测工作集调整配额或降低并发,并核对父级预算恢复原 cpu.max/unit 配置并重新加载,验证邻居不受影响
VPS steal 持续且与业务退化同窗跨窗口取证后迁移或向供应商升级保留原实例和切回条件,先验证新实例而不是直接销毁

一次只改一个变量,并在变更前保存配置、版本、队列深度、任务状态和基线。D 状态任务通常不能靠普通 signal 立即结束;强制重启可能让状态型服务丢失尚未持久化的数据。先明确应用一致性和存储恢复要求,再决定是否重启。

用同一窗口验收并保留精确回滚

  • 真实请求的错误率、延迟、吞吐和队列积压回到预先定义的范围,而不只是 load average 开始下降。
  • 连续区间内 vmstat 的 r/b、CPU/IO PSI 和 D 状态任务数量回到基线;1、5、15 分钟负载按窗口逐步收敛。
  • 若改了并发或 cgroup,目标 unit 的 throttling 增量下降,宿主及相邻服务没有新增 CPU、内存或 I/O 压力。
  • 若修了存储路径,await、队列、内核错误和真实读写共同恢复;不能只用一次空闲期 iostat 通过验收。
  • 任何指标恶化、数据检查失败或故障转移到依赖时,恢复保存的应用版本、并发、unit 或挂载配置,再重复同一组检查。

告警要同时保存负载的组成

持续采集 load average、当前 runnable/blocked、CPU/IO PSI、cgroup throttling、VPS steal、设备延迟与业务 SLO。告警应针对持续窗口和用户影响,并附部署、调度任务、备份与宿主事件;一个固定的 load 阈值无法跨 CPU 数、配额和工作负载通用。内核、sysstat、procps-ng、虚拟化或 cgroup 层级变化后,重新核对字段与采样语义。

本文命令在 2026-08-19 按 Linux 内核、Linux man-pages、procps-ng、sysstat 与 systemd 当前上游资料完成语义和语法审阅,没有在读者的 VPS、生产服务或合成压力环境中执行。旧内核可能没有 PSI,旧工具可能缺少选项,容器可能看到不同的 CPU、cgroup、设备和 `/proc` 边界;运行前先确认版本、权限、命名空间和独立恢复通道。

返回知识库