查看和切换块设备 I/O 调度器,理解队列、合并与延迟取舍
I/O 调度器(elevator)决定块层如何合并、排序和分发请求。现代内核使用 blk-mq 多队列框架,常见可选值为 none、mq-deadline、kyber、bfq(具体取决于内核配置);旧的 noop、deadline、cfq 已在 5.0 随传统单队列块层一并移除,由上述 blk-mq 调度器取代。
cat /sys/block/<dev>/queue/scheduler,方括号内是当前值。none;通用服务器和虚拟盘常用 mq-deadline;需要按进程公平性或桌面交互性时用 bfq,它开销更大。# 查看设备当前调度器(方括号内为当前值)
cat /sys/block/nvme0n1/queue/scheduler
# 查看设备是否旋转盘、当前调度器和容量(SCHED 列需较新的 util-linux)
lsblk -d -o NAME,ROTA,SCHED,SIZE
# 切换为 mq-deadline(虚拟盘先确认底层不会覆盖)
echo mq-deadline | sudo tee /sys/block/vda/queue/scheduler
# 查看预读和队列请求数
cat /sys/block/vda/queue/read_ahead_kb
cat /sys/block/vda/queue/nr_requests
# 用 udev 规则持久化:写入 /etc/udev/rules.d/60-iosched.rules
# ACTION=="add|change", KERNEL=="vda", ATTR{queue/scheduler}="mq-deadline"
# 观察调度效果:await、aqu-sz 与 %util 的变化
iostat -xz 1 5
ionice 设置的是 ioprio,但 none 调度器基本不理会它;容器场景更可靠的是 cgroup v2 的 io.max/io.weight。
none 理解成“关闭 I/O”是错的:它只是不做请求排序,依赖设备自身的多队列并发。Invalid argument 或被静默忽略。/sys/block/<dev>/queue/scheduler 不会持久化;升级内核、重新扫描设备或重启后恢复默认,需 udev/tuned。ionice 在 none 或部分 blk-mq 调度器下几乎无效;按容器限流应使用 cgroup io 控制器。none 会失去合并与排序,随机读写延迟可能明显变差。mq-deadline 不一定比 none 差,差异取决于负载和厂商队列;变更前后要用 iostat -xz 和尾延迟对比。nr_requests 不会自动提升吞吐,反而可能增加队列延迟;NVMe 尤其不建议盲目调大。%util、await 受宿主机其他租户和云盘限流影响,不能只看容器内视角。none 调度器适合什么设备,为什么?ionice 给容器限流通常不可靠?none 避免内核层额外排序,降低延迟和 CPU 开销。ionice 依赖块层调度器解释 ioprio,none 等调度器不处理;容器应按 cgroup v2 的 io.max/io.weight 限流。iostat -xz 的 r_await/w_await、aqu-sz、%util,以及应用侧 P99 延迟和吞吐;同时排除云盘限流。