先分清:CPU 瓶颈还是 IO 瓶颈
一个快速判断:在 `top` 里看 load average 与 CPU 使用率的关系。如果 load 很高(比如 8)但 `%Cpu(s)` 里的 us+sy 远低于 100%(比如 40%),说明大量进程处于不可中断睡眠(D 状态),在等 IO。`top` 里 D 状态进程那一列是关键证据。
更直接的证据是 `/proc/pressure/io`。读 `some avg10` 这一行:如果值接近或超过 10,说明最近 10 秒内平均有 10% 的时间「至少有一个任务因等 IO 而被卡住」。这个数字比任何单一设备指标都更贴近你感受到的「卡」。
注意负载高也可能是 D 状态之外的伪影(比如大量僵尸进程、容器里 fork 风暴)。所以判断顺序是:先看 D 状态进程数量,再看 PSI,再看 iostat,三者一致才能下结论。
如果 PSI io 的 some 很低但 load 依然高,去看 load 的来源:`ps -eo state,pid,comm | grep -c "^D"` 精确计数,再 `vmstat 1` 看 `b` 列(阻塞在 IO 上的进程数)。若 `b` 长期大于 0 且 `wa` 列很高,说明确实是 IO 等待而不是 CPU 争抢。
ps -eo state,pid,comm | awk '$1=="D"' | head
grep -E "some|full" /proc/pressure/io
vmstat 1 5 # 关注 wa(iowait)与 b(阻塞数)iostat -x:逐列读懂,别只盯着 %util
%util 只表示「采样窗口内设备非空闲时间占比」。对块设备它等价于「至少有一个请求在队列中的时间比例」,并不反映队列有多深。NVMe 这类并行设备可以 %util 100% 而吞吐与延迟完全正常——这是并行队列的正常表现,不是故障信号。
真正该看的是 await 与队列相关列。`r_await` / `w_await`(现代内核的 iostat -x 已拆分为读写分列)是平均请求延迟,包含排队时间。`%aqu-sz`(读请求平均排队时长)与 `%arq-sz` 分别表示读/写请求的平均队列深度,这两个指标升高意味着请求在排队等设备。
`r/s` `w/s` 是每秒请求数,`rkB/s` `wkB/s` 是吞吐。判断瓶颈形态看组合:await 高但 %arq-sz 低 → 设备本身慢(换盘或换存储类型);await 高且 %arq-sz 也高 → 队列拥塞,通常是并发太高或随机 IO 打满队列深度;await 低但 %util 高 → 大量小请求打爆并发度,是典型的「小文件风暴」。
采样间隔必须给对。`iostat -x 1`(间隔 1 秒)第一行是自开机以来的均值,没有参考价值——丢掉它,从第二行开始看。用 `iostat -x 1 10` 连看 10 次。若怀疑瞬时尖刺,用 `iostat -x 0.5 20` 提高时间分辨率。
iostat -x 1 10
# 关注列:r_await/w_await(平均延迟,含排队)
# %arq-sz / %aqu-sz(请求平均队列深度 / 排队时长)
# rkB/s、wkB/s(吞吐)、%util(只是非空占比)PSI:把「设备慢」翻译成「谁被拖慢」
PSI(Pressure Stall Information)统计的是「任务因某类资源不足而失去运行机会」的时间占比。它是唯一能把内核视角的等待映射到「有多少任务受影响」的指标,而且开销极低,适合长驻采集。
`/proc/pressure/io` 有两行:`some` 统计至少一个任务因 IO 被卡的时间比例,`full` 统计「所有非空闲任务都因 IO 被卡」的时间比例——后者意味着系统已经彻底空转,全都在等盘。`some` 反映个体体验,`full` 反映系统级停摆,两者同时高就是灾难。
还应把 PSI 与 cgroup 结合。systemd 服务可以用 `systemctl show <unit> -p IOReadPressureIOPressure` 等属性读取该单元的 IO 压力,或直接读 cgroup 的 `io.pressure` 文件。这样能直接回答「是哪个服务把机器拖垮了」。
监控侧建议对 `node_pressure_io_wait_seconds_total` 做 rate() 再对时间归一化,得到 PSI 占用比例并设阈值告警(比如 > 40% 持续 5 分钟)。同时把 `avg10/avg60/avg300` 作为一组时间窗一起看,能区分短尖刺与持续劣化。
cat /proc/pressure/io
cat /sys/fs/cgroup/system.slice/<unit>/io.pressure
systemctl show nginx.service -p IOReadPressureIOPressure按进程归因:谁在真正读写
iostat 告诉你「设备忙」,不告诉你「谁忙」。归因要用 `pidstat -d 1`,它按进程/线程统计 `kB_rd`、`kB_wr`、`kB_ccwr`(被取消的写请求——高值说明请求发出后被中断,浪费了带宽),`iodelay` 是该进程的 IO 延迟贡献。
pidstat 的输出是滚动的:每次刷新只显示本间隔内的值,所以要盯住 `kB_wr` 列的高值行,而不是看累计。用 `pidstat -d 1 20 | sort` 不现实(输出是流式的),实务中常见做法是连续观察几屏,记录下反复出现的 PID。
`fiotop` 适合交互式排查:它直接按 IO 速率排序列出进程,输出可读性好,比 pidstat 直观得多。缺点是默认依赖终端速度刷新,不适合脚本化,也不采历史。
如果 iotop 不可用,还有一个零依赖的办法:`/proc/<pid>/io` 暴露每个进程的 `read_bytes` / `write_bytes` 累计值(按实际落盘字节计,不含 page cache 命中)。间隔采样两次做差即可得到真实速率,适合在没有额外工具的最小化容器里应急。
pidstat -d 1
# 关注 kB_wr 高、kB_ccwr 也高的行:写风暴 + 请求被取消
cat /proc/$(pgrep -n nginx)/io | grep -E "read_bytes|write_bytes"
fiotop -o -d 1 # -o 按写入速率排序根因分型与对应处置
小文件风暴(高 %util、低 await、高 %arq-sz):典型的 inode 与 fsync 密集负载,元数据操作打满队列。处置优先合并小文件为少量大文件,或改用支持直接 IO 的数据库/存储格式;短期可加内存页缓存,但要注意这只是在推迟 IO 而非消除。
大文件顺序吞吐(低 await、高 %arq-sz):通常是备份、同步、导出任务。核对业务是否必要,挪到业务低峰或限速(`ionice -c3` 空闲优先级、`cgroup` 的 `blkio`/io 权重)都能立竿见影。这类负载调优空间最大,收益也最直接。
高随机写(await 高且延迟分布长):多半是数据库 checkpoint 或日志刷盘。检查日志/备份配置是否超出性能预期,以及是否误把存储卷设成同步提交。网络存储(尤其 NFS)上尤其明显,此时把 `/etc/fstab` 里的同步挂载选项与存储侧缓存策略一起看。
最后别忽略内核参数这一层:`/sys/block/*/queue/scheduler` 确认调度器(`none` 适合高并发 NVMe,`bfq` 适合桌面/低并发),`/sys/block/*/queue/nr_requests` 控制请求队列深度,`/sys/block/*/queue/rotational` 判断是否机械盘。参数写进 `/etc/sysctl.d/` 或 udev 规则才能重启后保持。
cat /sys/block/nvme0n1/queue/scheduler
# 高并发 NVMe 常用 none;低并发/桌面可试 bfq
cat /sys/block/nvme0n1/queue/nr_requests
cat /sys/block/nvme0n1/queue/rotational # 0 = 非机械盘