Linux I/O 调度器调整:mq-deadline、none、BFQ
16 分钟阅读 - 2026年6月1日

如何为 NVMe、SATA 和 HDD 工作负载选择和调整正确的 Linux I/O 调度器,包括 sysfs 命令、udev 规则和 fio 基准测试步骤。
Linux I/O 调度器调优:mq-deadline、none 和 BFQ
Linux I/O 调度器决定读写请求到达存储设备的顺序,而正确的选择几乎完全取决于您的硬件。使用 none 用于 NVMe, mq-deadline 适用于运行混合工作负载的 SATA SSD 和 HDD, bfq 当需要防止某个进程独占资源导致其他进程资源饥饿时。本指南将介绍这三种主要调度器的运作原理、如何根据工作负载选择合适的调度器,以及如何进行调优并验证结果。
若您希望在阅读前先进行实践操作,本视频介绍了如何通过终端切换和测试调度程序的基础知识。
mq-deadline、none 和 BFQ 的区别
每种调度器处理请求的策略各不相同。了解它们之间的差异,才能让你有针对性地进行选择,而不是在系统启动时直接使用内核默认选定的调度器。
mq-deadline
该 mq-deadline 调度器确保没有请求会无限期等待。它为读写操作分别维护独立的排序队列,按逻辑块地址(LBA)排序以减少寻道时间,并强制执行时限:默认情况下,读取请求的时限为 500 毫秒,写入请求为 5 秒。当请求达到其时限时,它会跳到队列的队首。
读取请求优先于写入请求,因为读取通常会阻塞应用程序,而写入则以异步方式处理。为了防止写入请求完全被饿死,调度器会在处理完一定数量的读取请求后,批量处理一批超时的写入请求。其结果是保持一致的低延迟,这使其非常适合数据库服务器以及任何混合了读写操作的工作负载。
无
该 none 调度器几乎不做任何处理。它会按照先入先出(FIFO)的顺序将请求直接传递给设备,不进行任何重新排序、合并或优先级设置。这非常适合现代 NVMe 驱动器,因为它们能够管理自身的内部队列,并能同时跟踪数万个正在处理中的请求。移除软件调度层提供了从应用程序到设备的最短路径,这正是高吞吐量 NVMe 工作负载所需要的。
但需注意的是,这仅在硬件能够自主智能调度时才有效。对于队列较浅的 HDD 或 SATA SSD,跳过软件重排序通常会导致性能下降,而非提升。
BFQ
BFQ(预算公平队列)将公平性置于首位。它不采用时间片机制,而是为每个进程分配以磁盘扇区为单位的预算。大型顺序读取任务获得更大预算以维持吞吐量,而对延迟敏感的任务则获得较小预算以便快速处理,同时运行中的反馈循环会动态调整这些预算。
即使在高负载下,BFQ也能保持交互式任务的响应性,因此当后台进行大文件传输时,视频播放或数据库查询仍能保持流畅。这种公平性需要消耗CPU资源。其每次请求的开销约为1.9微秒,大约是mq-deadline的三倍;在较慢的ARM核心上,该开销会将吞吐量限制在远低于相同调度程序在快速x86芯片上所能达到的水平。 对于最重视原始吞吐量和 CPU 效率的服务器而言,这种权衡难以令人信服。
| 调度器 | 算法 | CPU开销 | 最佳硬件 | 主要目标 |
|---|---|---|---|---|
mq-deadline | 带截止时间的排序LBA | 低(约0.7 µs/请求) | SATA SSD、HDD、虚拟磁盘 | 可预测的低延迟 |
none | FIFO,无重排序 | 可忽略不计 | NVMe SSD | 最大吞吐量 |
bfq | 按比例分配的配额 | 中等(约 1.9 微秒/请求) | HDD、共享及桌面系统 | 公平性与响应性 |
根据工作负载选择合适的调度器
选择合适的调度器取决于两点:您的存储硬件和应用程序的访问模式。首先考虑硬件。如果设备本身已具备请求重排序能力(例如配备支持重排序固件的 NVMe 驱动器),软件调度只会增加开销,因此 none 胜出。而在寻道时间占主导地位的机械硬盘上,软件重排序能降低延迟,因此 mq-deadline 或 bfq 是更优选。SATA SSD 则介于两者之间:速度快于 HDD,但缺乏 NVMe 的深度队列,因此 mq-deadline 最为合适。
当已有其他机制代为调度时,同样的逻辑同样适用。运行在 virtio-blk 上的客户机虚拟机依赖主机进行 I/O 调度,而带有写回缓存的硬件 RAID 控制器会自行优化请求顺序。在这两种情况下, none 都能避免重复消耗资源。
访问模式是第二个考量因素。每秒执行数千次4K随机读取的数据库,与从NVMe阵列流式读取大型顺序数据块的训练任务截然不同,它们需要不同的调度器。下表将常见工作负载与相应的起始点进行了对应。
| 工作负载 | 存储 | 调度器 | 原因 |
|---|---|---|---|
| AI/ML 训练 | NVMe SSD | none | 顺序高吞吐量;固件处理队列 |
| OLTP 数据库 | NVMe SSD | none | 低延迟随机 I/O;避免软件开销 |
| OLTP 数据库 | SATA SSD | mq-deadline | 防止写入饥饿;尾部延迟可预测 |
| 数据仓库 / OLAP | NVMe / 高速 SSD | none | 深度并行队列;最大吞吐量 |
| 通用网站托管 | SATA SSD / HDD | mq-deadline | 混合小文件 I/O 的稳定响应 |
| 共享/多租户托管 | HDD / SSD | bfq | 租户间资源分配公平;防止 I/O 独占 |
| 虚拟机客户机 | virtio-blk | none | 主机已进行调度;双重调度会浪费 CPU |
| 备份 / 归档 | HDD | mq-deadline | 具有饥饿保护的顺序吞吐量 |
有一点例外值得指出。即使在 NVMe 上,如果 p99 或 p999 的尾部延迟是您关注的指标(例如在金融系统中), mq-deadline 可以胜过 none 通过强制执行严格的截止时间并防止偶发的延迟请求。
更改和调整调度器参数
无论是切换调度器还是调整其参数,均通过 sysfs 进行,且无需重启即可测试更改。
切换活动调度器
检查设备可用的调度器列表,其中方括号内的值即为当前活动调度器:
cat /sys/block/sda/queue/scheduler在运行时切换至其他调度器。此操作立即生效,但重启后将失效:
echo bfq | sudo tee /sys/block/sda/queue/scheduler如果 bfq 未列出,请先加载模块:
sudo modprobe bfq若要使设置永久生效,请使用 udev 规则而非旧版 elevator= 内核参数,因为在 RHEL 9 及类似版本中,该参数已不再能更改调度器。此规则将 mq-deadline 为 /etc/udev/rules.d/60-io-scheduler.rules:
ACTION=="add|change", SUBSYSTEM=="block", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"无需重启即可重新加载并应用该规则:
sudo udevadm control --reload-rules && sudo udevadm trigger在基于 RHEL 的系统上,TuneD 配置文件通过全局配置文件而非逐设备规则实现相同功能。
值得调整的参数
每个调度器都在 /sys/block/<device>/queue/iosched/下提供其可调参数。对于 mq-deadline而言,时限是主要调节手段。运行在 SATA SSD 上的对延迟敏感的数据库将受益于更短的时限:
echo 100 | sudo tee /sys/block/sda/queue/iosched/read_expire
echo 1000 | sudo tee /sys/block/sda/queue/iosched/write_expire对于 bfq 在高吞吐量系统上,禁用延迟启发式算法可提升吞吐量:
echo 0 | sudo tee /sys/block/sda/queue/iosched/low_latency
echo 0 | sudo tee /sys/block/sda/queue/iosched/slice_idle| 调度器 | 参数 | 默认 | 调优目标 |
|---|---|---|---|
mq-deadline | read_expire | 500 毫秒 | 调低可加快读取响应 |
mq-deadline | write_expire | 5000 毫秒 | 降低该值可减少写入延迟 |
mq-deadline | writes_starved | 3 | 在读取密集型负载下增加 |
mq-deadline | fifo_batch | 16 | 设置为 1 以获得最低延迟 |
bfq | low_latency | 1 | 设置为 0 以获得最大吞吐量 |
bfq | slice_idle | 8 毫秒 | 若使用 SSD 或 RAID,请设置为 0 |
bfq | strict_guarantees | 0 | 若需严格带宽共享,请设为 1 |
对于共享主机,BFQ 与 cgroups v2 配合良好。分配 io.weight 特定值,例如可将数据库进程的 I/O 份额设为备份任务的十倍,从而确保后台任务不会挤占交互式流量。无论如何调整,在受 CPU 性能限制且 I/OPS 需求高的系统中,BFQ 较高的单请求开销会累积增加,因此请在正式启用前进行基准测试。
调优后的性能验证
在进行任何更改之前,务必先记录基准数据。否则,您将无法判断调整是否有效。
fio 是此类测试的标准工具。它通过块大小、队列深度和 I/O 引擎设置来重现特定的工作负载模式。请务必使用 --direct=1 参数,使其绕过页面缓存,直接测量调度器和设备性能,而非缓存读取。测试场景应与实际工作负载相匹配:
| 工作负载 | fio 参数 |
|---|---|
| OLTP 数据库 | --rw=randread --bs=4k --iodepth=32 --direct=1 |
| 数据仓库 | --rw=read --bs=1m --iodepth=32 --direct=1 |
| 预写/重做日志 | --rw=write --bs=4k --iodepth=1 --direct=1 |
| 对象存储 | --rw=randrw --bs=64k --iodepth=64 --direct=1 |
在 iodepth 1 到 256 的数值范围内运行相同的测试,以找出设备的饱和点——即 IOPS 停止攀升且延迟飙升的临界点。对于变更后的实时监控, iostat -x 1 报告关键指标: r_await 以及 w_await 用于读写完成延迟, aqu-sz 表示平均队列深度, %util 表示设备利用率。当 %util 该值接近 100% 时,说明硬件已达到极限,此时调整调度器也无济于事。
要区分软件开销与硬件开销,请配合 btt 运行 blktrace。它将延迟拆分为 Q2D(软件队列耗时)和 D2C(设备处理请求所需时间)。若 Q2D 占主导,则调度器是瓶颈;若 D2C 占主导,则硬件是瓶颈。
解读结果时需注意一点:调度器选择主要影响延迟分布的尾部,而非中位数。从 none 切换到 mq-deadline ,可能使中位数延迟增加几微秒,但会将 p99 和 p999 延迟减半。对于受 SLA 约束的用户级服务,这种权衡几乎总是值得的,这也是为何测量尾部延迟而非平均吞吐量才是此项工作的核心所在。
选择合适的调度器
调度器调优旨在将算法适配于硬件和访问模式,并通过测量进行验证。简而言之:
- NVMe:使用
none并让固件负责队列管理。 - 混合 I/O 的 SATA SSD 和 HDD:使用
mq-deadline以获得可预测的延迟。 - 共享或多租户主机:使用
bfq以防止单个工作负载占用过多资源而影响其他工作负载。 - 关注尾部延迟而非中位数:调度器变更会在 p99 和 p999 处体现,因此应重点测量这些指标。
- 确保持久化:使用 udev 规则或 TuneD,切勿使用已废弃的
elevator=参数。
要充分发挥任何调度器的性能,首先需要性能匹配的硬件。如果您需要专为高吞吐量、低延迟工作负载打造的 NVMe 服务器,请了解 FDC 的 VPS 方案。
Linux 内存管理:交换、OOM 杀手和 Cgroups
12 分钟阅读 - 2026年5月31日