LinuxNotes

第 17 章:IO 栈

zjc 于 2026-01-17 发布

这是《Linux 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 IO 栈连接文件系统和块设备。应用一次 write 可能经历页缓存、文件系统、通用块层、IO 调度器、设备驱动和云存储。定位 IO 问题要区分慢在应用、文件系统、块设备还是远端存储。

17.1 IO 栈全景

Application
  -> System Call
     -> VFS
        -> File System
           -> Page Cache
              -> Block Layer
                 -> IO Scheduler
                    -> Device Driver
                       -> Disk / SSD / Cloud Disk

同步 IO:

调用线程等待 IO 完成

异步 IO:

提交 IO 后继续执行
  -> completion notification

17.2 指标

延迟:

指标 含义
await IO 平均等待时间
r_await 读等待
w_await 写等待
svctm 旧指标,现代工具不推荐单独依赖

吞吐:

指标 含义
r/s、w/s IOPS
rkB/s、wkB/s 带宽
rrqm/s、wrqm/s 合并请求

队列:

指标 含义
aqu-sz 平均队列深度
rareq-sz 平均读请求大小
wareq-sz 平均写请求大小
util 设备忙碌时间占比
iostat -xz 1

17.3 iostat 解读

示例:

Device  r/s   w/s   rkB/s  wkB/s  await  aqu-sz  util
vdb     100   500   800    8000   20.00  10.0    95.00

判断:

现象 方向
await 高、util 高 设备压力高或规格不足
IOPS 高、吞吐低 小 IO
吞吐高、IOPS 低 大块顺序 IO
util 100% 但 await 低 SSD 并行能力强,util 语义有限
单进程 bo 高 应用刷盘或日志

util 在多队列设备上不能简单理解为“磁盘满”。要结合延迟、队列和容量指标。

17.4 进程 IO

pidstat -d 1
iotop -P -d 2
cat /proc/<pid>/io

/proc/<pid>/io 常见字段:

字段 含义
read_bytes 实际读块设备字节
write_bytes 实际写块设备字节
syscr 读系统调用次数
syscw 写系统调用次数
cancelled_write_bytes 被截断等方式抵消的写

应用写页缓存成功不代表已经落盘。

17.5 块大小与对齐

查看:

blockdev --getbsz /dev/vdb
blockdev --getss /dev/vdb
stat -f /data

影响:

  1. 数据库 page size;
  2. 文件系统分配组;
  3. RAID 条带大小;
  4. 小 IO 合并效率;
  5. 云盘性能规格。

生产不建议盲目手工调块大小,应以数据库、文件系统和存储厂商建议为准,并用真实 workload 验证。

17.6 IO 调度器

查看:

cat /sys/block/vdb/queue/scheduler
lsblk -D

常见策略:

策略 特点
none / noop 尽量不重排
mq-deadline 面向延迟目标
bfq 面向公平和交互
kyber 面向低延迟

修改:

echo mq-deadline | sudo tee /sys/block/vdb/queue/scheduler

云盘和 NVMe 设备常有默认优化策略。没有压测证据时不要随意修改。

17.7 直 IO 与缓冲 IO

Buffered IO:

write -> page cache -> writeback

Direct IO:

write -> block device

特点:

模式 优点 代价
Buffered 命中快、接口简单 双缓存、回写时机不确定
Direct 应用自控缓存、减少拷贝 对齐要求高、实现复杂

数据库引擎通常按自身配置决定 direct IO、fdatasync、O_DSYNC 等策略。

17.8 预读

查看:

cat /sys/block/vdb/queue/read_ahead_kb
blockdev --getra /dev/vdb

调整:

sudo blockdev --setra 4096 /dev/vdb

顺序扫描可能受益,随机读可能浪费缓存。调整后必须用真实业务压测验证。

17.9 压测与验证

fio 示例:

sudo fio --name=rand-read \
  --filename=/data/fio.test \
  --direct=1 \
  --rw=randread \
  --bs=4k \
  --ioengine=libaio \
  --iodepth=32 \
  --numjobs=4 \
  --runtime=60 \
  --time_based \
  --group_reporting

写测试:

sudo fio --name=rand-write \
  --filename=/data/fio.test \
  --direct=1 \
  --rw=randwrite \
  --bs=8k \
  --ioengine=libaio \
  --iodepth=64 \
  --numjobs=4 \
  --runtime=60 \
  --time_based \
  --group_reporting

注意:

  1. 只在测试盘执行;
  2. 写测试会破坏数据;
  3. 记录文件系统和云盘规格;
  4. 记录队列深度和块大小;
  5. 和数据库真实模型分开评估。

17.10 常见故障

数据库写入延迟高

iostat -xz 1
pidstat -d 1
cat /proc/meminfo | grep -E 'Dirty|Writeback'

检查:

  1. checkpoint;
  2. redo 刷盘;
  3. 云盘规格;
  4. 快照并发;
  5. 同宿主机干扰;
  6. 慢 SQL 放大读。

应用 fsync 慢

strace -T -e trace=fsync,fdatasync -p <pid>
iostat -xz 1

评估:

  1. 存储延迟;
  2. fsync 频率;
  3. 批量提交能力;
  4. 持久化语义;
  5. 文件系统行为。

NFS 延迟高

mount | grep nfs
nfsstat -rc
ping -c 100 <nfs-server>
mtr -rwzc 100 <nfs-server>

本章小结

IO 分析要把延迟、IOPS、吞吐、队列和进程写入对应起来。await 是核心指标,util 只能作参考。生产优化前先确认存储规格和真实 workload,再用压测验证调度器、预读、直 IO 和应用刷盘策略。

思考题

  1. IOPS 和吞吐分别适合评估什么 workload?
  2. await 高说明什么?
  3. buffered IO 和 direct IO 有什么差异?
  4. 为什么 fsync 频繁会造成延迟?
  5. 设计一个数据库盘的 fio 测试方案。