系统卡顿排查到最后一层,十有八九是磁盘 IO。CPU 不忙、内存也够,但 wa 就是下不去,load 莫名其妙地高------很可能有一堆进程卡在 D 状态等磁盘。磁盘是整台机器里最慢的部件,也是最容易被后台任务拖垮的。这一篇用 iostat、iotop、pidstat -d、fio 四个工具,教你看懂磁盘的四个核心指标,并定位到具体是哪个进程在制造 IO。
一、先搞懂四个核心指标
看磁盘性能,无论用什么工具,都是围绕这四个数字转:
| 指标 | 含义 | 单位 |
|---|---|---|
| IOPS | 每秒完成多少次 IO 请求 | 次/秒 |
| 吞吐量(带宽) | 每秒读写多少数据 | MB/s |
| 延迟(await) | 每个 IO 平均耗时(含排队) | ms |
| 队列深度(aqu-sz) | 排队等待处理的 IO 数量 | 个 |
它们的关系,用生活比喻最好懂:
IOPS 是「收银台每秒服务几个人」,吞吐是「每秒卖出多少货」,延迟是「每个顾客平均等多久」,队列深度是「队伍里排了多少人」。
小文件、随机读写 场景(数据库、日志)看 IOPS + 延迟 ;大文件、顺序读写 场景(备份、视频)看 吞吐。别混。
二、iostat:磁盘全景统计
iostat 属于 sysstat,是最常用的磁盘性能工具。
安装
sudo apt install sysstat -y # Ubuntu
sudo dnf install sysstat -y # Rocky/CentOS
语法格式
iostat [选项] [间隔] [次数]
常用参数
| 参数 | 说明 |
|---|---|
-d |
只看磁盘,不显示 CPU |
-x |
显示扩展统计(含 await、%util),最常用 |
-m |
以 MB/s 显示吞吐 |
-k |
以 KB/s 显示(默认) |
-p |
显示分区级 |
1 5 |
每秒采样,共 5 次 |
示例与预期输出
iostat -dxm 1 3
预期输出:
Device r/s rMB/s rrqm/s %rrqm r_await rareq-sz w/s wMB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
sda 2.50 0.10 0.00 0.00 1.20 40.00 120.00 15.30 8.00 6.25 8.40 130.00 0.98 95.20
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.50 0.02 0.00 0.00 0.80 36.00 0.00 0.10
重点列解读:
| 列 | 含义 | 判断信号 |
|---|---|---|
r/s w/s |
每秒读/写请求数 | 即 IOPS |
rMB/s wMB/s |
读/写吞吐 | 看带宽用 |
r_await w_await |
读/写平均延迟(ms) | 关键!高于设备基线就是有问题 |
aqu-sz |
平均队列深度 | 持续 > 1 说明排队严重 |
%util |
设备忙碌时间百分比 | 接近 100% 说明饱和 |
rareq-sz wareq-sz |
平均每次请求大小(KB) | 判断是随机小 IO 还是顺序大 IO |
⚠️ 关于 %util 的经典误区
%util 接近 100% 常被当作「磁盘满了」的铁证,但对 SSD/NVMe 它非常具有误导性------SSD 内部能并行处理多个请求,%util 100% 时性能可能还很充沛。
判断磁盘是否真饱和,更可靠的是 await(延迟)和 aqu-sz(队列深度)范疇:
await明显高于设备正常水平 → 有压力aqu-sz持续大于 1 → 请求在排队
单看 %util,机械盘还算靠谱,SSD 上要结合延迟一起看。
三、iotop:看到底是谁在 IO
iostat 告诉你是「哪块盘」忙,但不知道「哪个进程」在搞。iotop 补上这一环,类似 top 但按 IO 排序。
安装
sudo apt install iotop -y # Ubuntu
sudo dnf install iotop -y # Rocky/CentOS
语法格式与常用参数
sudo iotop [选项]
| 参数 | 说明 |
|---|---|
-o |
只显示正在进行 IO 的进程 |
-b |
批处理模式,适合写脚本 |
-n N |
采样 N 次后退出 |
-P |
只显示进程,不显示线程 |
-k |
用 KB/s 显示 |
-a |
显示累计 IO,而非实时速率 |
示例与预期输出
sudo iotop -oP
预期输出:
Total DISK READ: 1.20 M/s | Total DISK WRITE: 15.30 M/s
PID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
1234 be/4 mysql 0.00 B/s 8.50 M/s 0.00% 0.30% mysqld
5678 be/4 root 800.0 K/s 6.00 M/s 0.00% 0.80% rsync --daemon
-o 过滤掉零 IO 的进程,-P 只看进程,输出干净。第一行的 Total 是全局读写速率。IO> 列是该进程等待 IO 的时间占比,越高越卡。
注意:iotop 需要 root 权限(要读 /proc/*/io),普通用户会报 CONFIG_TASK_IO_ACCOUNTING 或权限相关错误。
四、pidstat -d:无交互的进程级 IO
iotop 是交互式,不适合记录。要做数据采集,用 pidstat -d(同属 sysstat)。
pidstat -d 1 5
UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
root 1234 0.00 8704.00 0.00 120 mysqld
root 5678 800.00 6144.00 0.00 85 rsync
其中 iodelay(较新内核)是进程被 IO 阻塞的时钟节拍数,是比速率更能反映「被 IO 拖累」的指标。
五、fio:磁盘基准测试
上面三个是「看现状」,fio 是「测能力」------磁盘到底能跑多少 IOPS、多少带宽、延迟多高。换机器、调优前后对比,都靠它。
安装
sudo apt install fio -y # Ubuntu
sudo dnf install fio -y # Rocky/CentOS
语法格式
fio [选项] 作业文件
常用参数
| 参数 | 说明 |
|---|---|
--name=xxx |
作业名 |
--rw=randread |
随机读;read/write/randwrite/rw 等 |
--bs=4k |
块大小 |
--iodepth=32 |
IO 队列深度 |
--numjobs=4 |
并发作业数 |
--size=1G |
每个作业读写数据量 |
--runtime=60 |
运行 60 秒 |
--time_based |
到时间才停 |
--direct=1 |
绕过 page cache,测试真实磁盘 |
--filename=/dev/sdb |
目标设备或文件 |
示例与预期输出
测 4K 随机读的 IOPS(数据库典型场景):
sudo fio --name=randread --ioengine=libaio --direct=1 \
--rw=randread --bs=4k --iodepth=32 --numjobs=4 \
--size=1G --runtime=60 --time_based --group_reporting
预期输出(节选):
read: IOPS=98.5k, BW=385MiB/s (403MB/s)
lat (usec): min=8, max=2000, avg=32.40
iops : min= 95400, max=101200, avg=98520
测顺序写带宽(日志/备份场景):
sudo fio --name=seqwrite --ioengine=libaio --direct=1 \
--rw=write --bs=1M --iodepth=16 --size=1G --runtime=30 --time_based
write: IOPS=520, BW=520MiB/s
⚠️ fio 使用禁忌
绝对不要对生产盘直接跑写测试。 --filename=/dev/sdb 会覆盖数据。测试前:
- 用空文件 或专门的测试盘/测试分区
- 或在文件系统上用
--filename=/tmp/testfile(非裸设备)并确认路径安全
--direct=1 绕过缓存,结果更真实;不带它测的是「缓存速度」,没有参考价值。
六、一张表学会看懂 iostat
把 iostat -dx 的输出和判断标准对应起来:
| 现象 | 可能原因 |
|---|---|
w/s 高、wareq-sz 小(几 KB) |
大量随机小写,日志/数据库常见 |
rMB/s 高、rareq-sz 大(几百 KB+) |
顺序大读,备份/视频 |
await 远高于设备基线 |
磁盘压力大或队列深 |
aqu-sz 持续 > 1 |
请求排队,磁盘响应不过来 |
%util 100% 但 await 很低 |
SSD 正常,吞吐很可能没满 |
%util 100% 且 await 很高 |
真饱和,是瓶颈 |
七、⚠️ 常见错误
- 拿 %util 当唯一标准 。SSD 上
%util100% 不代表瓶颈,要看await和吞吐是否达到设备上限。 - 测磁盘不带
--direct=1。测出来的是 page cache 速度(几个 GB/s),跟真实磁盘无关。 - 在生产盘上跑 fio 写测试。直接覆盖数据,事故级别。测试要用专门介质。
- 忽视
D状态进程 。ps里一堆D、load高但 CPU 闲,这就是 IO 瓶颈的典型信号,去查iotop。 - 只盯平均延迟 。
await是平均值,可能被大量快请求拉低。要结合分布看(iostat看不了分布,需bpftrace/blktrace或fio的延迟百分位)。 - 忽略文件系统层 。磁盘快不代表文件系统快,
ext4的 journal、XFS的分配策略都会影响实际性能。
八、原理探究:机械盘 vs SSD,以及 IO 调度器
机械盘(HDD)的物理限制
机械盘有一个转动的盘片和一个移动的磁头。每次读写要 寻道(seek) 把磁头移到目标柱面,再 **旋转(rotational latency)**等到扇区转到磁头下。这两步是纯机械动作,毫秒级。
这决定了 HDD:
- 随机 IO 极慢(每次都要寻道,100~200 IOPS 到头)
- 顺序 IO 快得多(磁头不用大幅移动,可达 100+ MB/s)
所以 HDD 上,把随机访问改成顺序访问是最大优化点(比如 LSM 树 vs B+ 树、追加写日志)。
SSD 的特点
SSD 没有机械动作,靠闪存芯片,随机和顺序差距没 HDD 那么大,天生擅长随机 IO(几万到几十万 IOPS)。但有两个特性要注意:
- 写放大:闪存以「块」为单位擦除,改写要读-改-写,实际写入量可能大于请求量。
- 寿命(TBW):闪存有擦写次数上限,fio 压测要适可而止。
IO 调度器
内核在块设备层有调度器,决定 IO 请求怎么合并、排序、下发:
| 调度器 | 适用 | 特点 |
|---|---|---|
none(noop) |
SSD/NVMe | 不排队不排序,直接下发,SSD 自己有内部调度 |
mq-deadline |
通用 | 保证请求在期限内处理,防饿死 |
kyber |
高性能 SSD | 按读/写/丢弃分别限流 |
bfq |
桌面/低延迟 | 按进程分配带宽,交互友好 |
查看和设置:
# 查看当前设备调度器
cat /sys/block/sda/queue/scheduler
# 临时改成 none(SSD 推荐)
echo none | sudo tee /sys/block/sda/queue/scheduler
# 永久:写 udev 规则或内核参数 elevator=
SSD 上通常用 none/mq-deadline ,把调度交给硬件;机械盘可用 bfq 或 mq-deadline 优化交互。
九、IO 排查实操流程
# 1. 系统整体 IO 概况
iostat -dxm 1 5
# 2. 找制造 IO 的进程
sudo iotop -oP
# 3. 记录进程级 IO(可写脚本)
pidstat -d 1 5
# 4. 看是否有进程卡在 D 状态
ps -eo state,pid,cmd | grep "^D"
# 5. 查看脏页回写压力
grep -E "Dirty|Writeback" /proc/meminfo
# 6. 测设备真实能力(非生产盘)
sudo fio --name=t --rw=randread --bs=4k --iodelay... # 见上文
一条经验:磁盘问题里,80% 是「谁在乱写」而不是「磁盘坏了」。日志暴涨、备份撞车、数据库全表扫描、临时表落盘,这些应用层行为才是元凶。先找到那个进程,再谈优化磁盘本身。