iostat 是 sysstat 工具包中的磁盘 I/O 观测命令。它能回答三类常见问题:磁盘当前有多少 IOPS、读写带宽和延迟是多少,以及压力集中在哪块块设备上。
本文重点记录实时采样、添加时间戳、筛选 NVMe 设备、保存和查询日志,以及如何避免把 %util=100% 直接等同于“磁盘已经跑满”。
安装与快速开始
Debian、Ubuntu:
1 | sudo apt install sysstat |
RHEL、CentOS、Rocky Linux:
1 | sudo dnf install sysstat |
最常用的实时观测命令是:
1 | iostat -x 1 |
-x:显示扩展设备指标;1:每 1 秒输出一次,直到按Ctrl+C停止。
若只采样 5 次:
1 | iostat -x 1 5 |
一条适合排障的命令
1 | S_TIME_FORMAT=ISO iostat -d -x -t -y -z nvme0n1 nvme1n1 1 |
| 参数 | 作用 |
|---|---|
-d |
只显示设备报告,不显示 CPU 报告 |
-x |
显示 IOPS、带宽、延迟、队列和利用率等扩展指标 |
-t |
为每组报告打印时间戳 |
-y |
跳过“自系统启动以来”的第一组累计平均值 |
-z |
隐藏采样周期内完全没有活动的设备 |
nvme0n1 nvme1n1 |
只查看指定设备 |
1 |
每秒持续采样 |
S_TIME_FORMAT=ISO 让时间戳使用便于排序和查询的 ISO 格式。若系统上的 iostat 不支持某个参数,先用下面的命令核对版本和帮助:
1 | iostat -V |
为什么每秒执行一次,结果却完全相同
iostat 的第一组报告默认不是“当前一秒”,而是从系统启动到现在的平均值;后续报告才表示相邻采样之间的变化。
下面这种循环会反复启动新进程,每次都只取第一组累计值,因此多行结果可能几乎不变:
1 | # 不推荐 |
应让同一个 iostat 进程持续采样,并用 -y 丢弃第一组累计值:
1 | S_TIME_FORMAT=ISO iostat -x -t -y nvme0n1 nvme1n1 1 |
如果只需要 60 个有效样本:
1 | S_TIME_FORMAT=ISO iostat -x -t -y nvme0n1 nvme1n1 1 60 |
核心字段怎么看
不同 sysstat 版本显示的列略有差异,排障时优先关注下面这些字段:
| 字段 | 含义 | 判断方式 |
|---|---|---|
r/s |
每秒完成的读请求数 | 读 IOPS,已是合并后的块层请求数 |
w/s |
每秒完成的写请求数 | 写 IOPS,已是合并后的块层请求数 |
rkB/s |
每秒读取的数据量 | 实际按 KiB/s 计算,尽管表头通常写作 kB/s |
wkB/s |
每秒写入的数据量 | 除以 1024 可近似换算为 MiB/s |
r_await |
读请求平均完成时间 | 单位毫秒,包括排队和设备处理时间 |
w_await |
写请求平均完成时间 | 单位毫秒,包括排队和设备处理时间 |
aqu-sz |
平均队列长度 | 老版本中叫 avgqu-sz |
rareq-sz |
平均读请求大小 | 单位 KiB |
wareq-sz |
平均写请求大小 | 单位 KiB |
%util |
采样周期内设备存在 I/O 的时间占比 | 对串行设备接近 100% 时可能饱和;对 NVMe、RAID 不能单独作为性能上限依据 |
老版本中的 avgrq-sz 以扇区为单位,新版本的 areq-sz 以 KiB 为单位;avgqu-sz 后来改名为 aqu-sz。分析历史日志时应先保留并确认表头,不能只看没有列名的数据行。
IOPS 怎么算
若只考虑普通读写:
1 | 总 IOPS ≈ r/s + w/s |
例如:
1 | r/s=45.32 w/s=579.78 |
总 IOPS 约为:
1 | 45.32 + 579.78 = 625.10 |
这是当前业务实际发给块设备并完成的 IOPS,不是磁盘通过压力测试能够达到的最大 IOPS。
带宽和请求大小怎么算
假设某块盘的数据为:
1 | w/s=579.78 wkB/s=12858.13 |
写带宽约为:
1 | 12858.13 / 1024 ≈ 12.56 MiB/s |
平均每个写请求约为:
1 | 12858.13 / 579.78 ≈ 22.18 KiB |
因此,数据行中的 12858.13 表示写带宽,不是 IOPS;是否正常还要结合业务负载、请求大小和设备基线判断。
%util=100% 是否等于磁盘跑满
不一定。
%util 表示采样周期内,有 I/O 请求被发往设备的时间占比。对一次只能串行处理一个请求的设备,它接近 100% 往往意味着饱和;但 NVMe SSD、RAID 和其他能并行处理请求的设备,即使 %util=100%,仍可能继续提高并发和吞吐。
判断是否存在存储瓶颈,至少要同时观察:
r_await、w_await是否相对正常基线持续升高;aqu-sz是否持续增长;- IOPS 或带宽是否已经接近该设备在相同块大小、读写模式和队列深度下的实测上限;
- 应用延迟是否与上述变化出现在同一时间窗;
- CPU、网络、文件系统和应用自身是否存在更早出现的瓶颈。
同理,CPU 的 %iowait 低不能证明磁盘没有瓶颈,%iowait 高也不能单独证明某块磁盘已经饱和。
记录到日志文件
持续记录指定设备:
1 | S_TIME_FORMAT=ISO iostat -d -x -t -y -z nvme0n1 nvme1n1 1 \ |
只记录 10 分钟:
1 | timeout 10m env S_TIME_FORMAT=ISO \ |
放到后台运行:
1 | nohup env S_TIME_FORMAT=ISO \ |
iostat 主要读取内核维护的统计信息,通常开销很低;长期运行时更需要关注的是日志文件自身的空间占用。生产环境建议设置采样期限或使用 logrotate 做轮转。
查询历史日志
查询指定时间范围
假设时间戳格式为 YYYY-MM-DD HH:MM:SS,可提取 15:00 到 15:10 的完整报告:
1 | awk -v start='2025-06-09 15:00:00' \ |
ISO 格式按年月日从大到小排列,可以直接进行字符串范围比较。
只看某块设备
1 | awk ' |
临时查看时也可以直接使用:
1 | grep -E '^[0-9]{4}-|^nvme0n1[[:space:]]' iostat.log |
脚本需要稳定消费数据时,可考虑有限次数采样后输出 JSON:
1 | iostat -d -x -y -o JSON nvme0n1 1 5 > iostat.json |
JSON 字段会随 sysstat 版本增加,不应依赖字段顺序。
ZFS 场景如何配合观察
iostat 看到的是 Linux 块设备层的物理 I/O;zpool iostat 看到的是 ZFS pool 和 vdev 的逻辑 I/O。写合并、副本或 RAID 冗余都可能让两层数值不同,因此排查 ZFS 时应同时观察。
查看底层设备:
1 | S_TIME_FORMAT=ISO iostat -d -x -t -y nvme0n1 nvme1n1 1 |
查看 ZFS pool 和 vdev,并显示日期时间:
1 | zpool iostat -v -T d -y 1 |
同样不要用循环反复执行 zpool iostat -v 1 1:它的第一组报告也默认是系统启动以来的平均值。让命令持续运行,或使用支持的 -y 跳过第一组累计值。
一套实际排障顺序
- 用
lsblk确认真实设备名,不要把控制器名、旧设备名或不存在的别名直接写进采集命令; - 执行
iostat -x -t -y <device> 1,保存带表头的连续样本; - 先对齐应用异常的时间窗,再看 IOPS、带宽、延迟、队列和
%util; - 用同一块设备、相同块大小和读写模式的历史数据或
fio基准作对照; - ZFS 环境再结合
zpool iostat,定位差异发生在 pool、vdev 还是底层设备; - 若设备指标正常,继续检查 CPU、网络、文件系统和应用并发,不要为了“跑满磁盘”而盲目增加压力。