≡articles //snippets ./categories $uses ~about /search

Linux 负载排查方法论:Load Average 8.5、CPU 空闲 88%,从数字到底层 IO

服务器响应时间从 80ms 飙升到 3400ms。SSH 上去看,CPU 12%,内存还剩 600MB,磁盘 15G 空闲。没有一个指标触顶,但 Load Average 是 8.5,机器只有 2 个核。

这篇文章从那次告警开始,把 Linux Load Average 排查的核心指标(Load Average、IO Wait、CPU Steal)逐个拆解。不是概念解释,是从具体数字到具体根因的完整追踪。

Linux Load Average 排查起点:8.5,但 CPU 空闲 88%

$ uptime
 02:14:32 up 97 days, 11:22,  1 user,  load average: 8.52, 7.31, 5.18

三个数字分别是 1 分钟、5 分钟、15 分钟的系统平均负载。趋势在上升,说明问题在恶化。

$ top -bn1 | head -5
top - 02:15:01 up 97 days, 11:23,  1 user,  load average: 8.47, 7.35, 5.22
Tasks: 142 total,   1 running, 141 sleeping,   0 stopped,   0 zombie
%Cpu(s):  4.2 us,  2.1 sy,  0.0 ni, 88.5 id,  4.8 wa,  0.0 hi,  0.4 si,  0.0 st
MiB Mem :   1987.2 total,    594.1 free,    912.3 used,    480.8 buff/cache

关键数字:

  • us 4.2 — 用户态 CPU 使用率 4.2%
  • sy 2.1 — 内核态 2.1%
  • id 88.5 — 空闲 88.5%
  • wa 4.8 — IO Wait 4.8%
  • st 0.0 — CPU Steal 0%

CPU 几乎空闲,Load Average 8.5。这种情况基本只有一个解释:进程在等 IO。

第一层:Load Average 到底是什么

Load Average 不是 CPU 使用率。它是 处于 Runnable 状态(等 CPU)和 Uninterruptible Sleep 状态(等 IO)的进程数之和。内核通过 /proc/loadavg 暴露这个值:

$ cat /proc/loadavg
8.52 7.31 5.18 14/831 1982451

最后两个字段:14/831 — 当前 14 个线程在运行队列中(Runnable),总共 831 个线程存在。1982451 — 系统启动以来创建过的最大 PID。

14 个线程在等调度 + CPU 空闲 88% → 这些线程不是在等 CPU,是在等 IO。等 IO 的进程被标记为 D 状态(Uninterruptible Sleep),它们的“等待时间”被计入 Load Average 但不消耗 CPU。

$ ps aux | awk '{if($8=="D") print $0}'
www-data  28145  0.0  0.3 398456 67892 ?  D  02:10  0:03 php-fpm: pool www
www-data  28167  0.0  0.3 398456 67892 ?  D  02:12  0:05 php-fpm: pool www
www-data  28201  0.0  0.3 398456 68012 ?  D  02:14  0:07 php-fpm: pool www
www-data  28234  0.0  0.3 398456 67904 ?  D  02:14  0:06 php-fpm: pool www
www-data  28266  0.0  0.3 398456 67892 ?  D  02:15  0:04 php-fpm: pool www

# 5 个 PHP-FPM 进程卡在 D 状态

5 个 PHP-FPM 进程在 D 状态,它们在等磁盘 IO 返回。Load Average 8.5 的真相:5 个进程等 IO + 约 3 个进程排队等 CPU = Load Average ≈ 8。

第二层:IO Wait 4.8% — 不高啊?

IO Wait 只有 4.8%,这不严重。但 IO Wait 是一个瞬时采样值,反映的是 CPU 在采样瞬间花费在等待 IO 上的时间百分比。如果 IO 是突发的(比如每 5 秒一次大写入)IO Wait 在大多采样点显示 0,但在写入瞬间可能飙升到 80%。

看磁盘级别的 IO 指标能看得更清楚:

$ iostat -x 1 3
Device   r/s   w/s   rkB/s   wkB/s  await  r_await  w_await  svctm  %util
vda1    2.00  85.0   32.0  4232.0  48.32    2.50    54.20   4.21  36.58

# 第二次采样 — 突发写入
vda1    2.00 342.0   32.0  22340.0 286.50   3.00   295.12  2.48  85.20

# 第三次 — 回到正常
vda1    1.00  62.0   16.0  3190.0  42.10    2.00    46.30   4.10  25.80

第二次采样:w/s 342(每秒 342 次写操作)、w_await 295ms(每次写/平均等待 295ms)、%util 85.2%(磁盘在 85% 的时间内忙于处理请求)。

磁盘存在周期性的大写入突发。写入量从 4MB/s 跳到 22MB/s,w_await 从 54ms 跳到 295ms。平均 IO Wait 只有 4.8% 是因为采样间隔 1 秒内大部分时间磁盘空闲,但在突发瞬间队列深度爆满。

谁在写?iotop 抓现行

$ sudo iotop -aoP -n 5

Total DISK WRITE:     22.34 M/s
  TID  PRIO  USER     DISK WRITE  COMMAND
28145 be/4  www-data     18.40 M  php-fpm: pool www
 3120 be/4  mysql         3.20 M  mariadbd
28145 be/4  www-data      0.74 M  php-fpm: pool www

PHP-FPM 进程在疯狂写盘。查一下它在写什么:

$ sudo lsof -p 28145 | grep -E 'REG.*w$'
php-fpm8. 28145 www-data  14u  REG  253,1  146800640  524389 /tmp/php_session_abc123

PHP session 文件。一个 140MB 的 session 文件在持续写入,WordPress 的某个插件把大量数据序列化到 $_SESSION 里,每次请求结束时 PHP 把整个 session 对象刷新到磁盘。140MB × 每秒几次请求 = 磁盘 IO 被打爆。

修复:session 切 Redis + 控制序列化体积

# wp-config.php — 切 Redis session handler
define('WP_REDIS_SESSION', true);
define('WP_REDIS_SESSION_MAX_TTL', 3600);

# 同时在 mu-plugin 限制 session 数据
add_action('shutdown', function() {
    if (isset($_SESSION) && strlen(serialize($_SESSION)) > 512000) {
        error_log('Session too large: ' . strlen(serialize($_SESSION)) . ' bytes');
        $_SESSION = array_slice($_SESSION, -20, 20, true);
    }
}, 100);

验证,切完后 Load Average 从 8.5 降到 0.8:

$ uptime
 02:28:14 up 97 days, 11:36,  1 user,  load average: 0.82, 2.14, 3.92

第三层:CPU Steal — 云服务器的隐形杀手

上面修的是 IO Wait 问题。但还有一种情况下 Load Average 也会虚高而 CPU 空闲,CPU Steal。

CPU Steal(st 在 top 里)表示的是:虚拟机想用 CPU 但 hypervisor 把 CPU 时间片分配给其他虚拟机了。宿主机超卖时,你的 vCPU 并非真正可用,每次调度都先被 hypervisor 截胡。

$ top -bn1 | grep -oP '%Cpu.*'
%Cpu(s):  12.3 us,  4.1 sy,  0.0 ni, 68.2 id,  0.8 wa,  0.0 hi,  0.2 si, 14.4 st

st 14.4,14.4% 的 CPU 时间被 hypervisor 偷走了。这种情况下你看到的 CPU 使用率是虚低的:进程想跑但 hypervisor 不给时间片,进程卡在 Runnable 状态但不在 CPU 上执行,被计入 Load Average。

# 持续监控 Steal — 单次 top 可能漏掉
$ sar -u 5 10 2>/dev/null | grep -v '^$|^Linux|^Average|CPU'

02:30:01        CPU     %user     %nice   %system   %iowait    %steal     %idle
02:30:06        all      10.20      0.00      3.40      0.80     16.40     69.20
02:30:11        all       8.60      0.00      2.80      0.60     22.30     65.70
02:30:16        all      11.40      0.00      3.20      1.10     15.80     68.50

# Steal 在 15-22% 之间波动——宿主机严重超卖

用 perf 进一步确认不是在虚拟机内部等锁:

$ sudo perf stat -e 'kvm:kvm_exit' -a sleep 10 2>&1 | grep kvm_exit

  823,445      kvm:kvm_exit

# 10 秒内 82 万次 VM Exit——hypervisor 在频繁切换 vCPU

正常负载下 VM Exit 每秒几千到几万次。82 万次/10 秒 = 8.2 万次/秒,说明宿主机调度极度拥挤,每次你的 VM 刚拿到 CPU 就被 hypervisor 切出去。

确认是否受影响:/proc/stat 的 guest 和 steal 时间

$ head -1 /proc/stat
cpu  41523841 4823 12893401 983427382 284721 0 18234 0 0 0

# 字段含义(按顺序):
# user nice system idle iowait irq softirq steal guest guest_nice

steal=18234(单位是 jiffies,每 jiffy = 10ms 通常)。这台 VM 从启动以来被偷走了 182340ms ≈ 3 分钟的 CPU 时间。对比 idle=983427382(983 万秒的空闲),Steal 占比很小,说明这台机器不是偶尔被偷,是闲的时候没事,忙的时候 hypervisor 抢你的时间片。

修复:没有技术修复——这是云服务商问题

CPU Steal 不是你可以通过改配置修复的。如果你在低端 VPS(年付 $20 以内的那种)上看到 Steal > 10%,换一家云服务商是唯一方案。买之前用 stress-ng --cpu 2 --timeout 60s 打满 CPU 后跑 sar -u 1 10 看 Steal 值。

排查路径总结

# 一分钟定位流程
$ uptime                         # 1. Load Average 多少?趋势?
$ top -bn1 | head -5             # 2. CPU 空闲还是 IO Wait 高?
$ ps aux | awk '$8=="D"'        # 3. 有 D 状态进程吗?
$ iostat -x 1 5                 # 4. 磁盘在忙吗?await 多高?
$ sar -u 5 3                    # 5. Steal 在偷吗?
$ iotop -aoP -n 3               # 6. 谁在读写?

六条命令,从 Load Average 一路追到具体进程和具体磁盘操作。不需要 Grafana、不需要 Prometheus,半夜排查问题,这几条命令就是全部。

如果你在排查 VPS 性能问题时需要系统化的工具集,之前写的 VPS 性能优化工具清单 里有更多排查手段。关于 IO Wait 导致的 MySQL 慢查询,MySQL 慢查询排查实录 里有数据库层的分析。如果涉及 Nginx 缓存导致的磁盘 IO 问题,那篇我也写了完整的链路。Load Average 的第一个概念定义来自 Brendan Gregg 的经典文章。

© 2026 本文由 Charlie 原创撰写,发布于 sodebug.com。未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。

独立开发者,在 WordPress、Nginx 和各种 API 之间切换。不写没用的东西。

相关文章

VPS 常用测试脚本大全5 minNginx FastCGI Cache 缓存清除完整指南:为什么改了首页却看不到更新5 minsystemd timer 替代 cron 实战:从 crontab 静默清空到全量迁移9 min两台 VPS 跑个分:磁盘 IO 差 55 倍的实际体验7 min
$ echo "less bullshit, more debugging" | send-to-inbox
新文章直接发到邮箱,一个月 2-4 封。
订阅