Li Sheng | Backend / Distributed Storage Engineer Li Sheng | Backend / Distributed Storage Engineer
Home
Resume
Projects
Topics
Notes
GitHub (opens new window)
Home
Resume
Projects
Topics
Notes
GitHub (opens new window)
  • Go语言

  • C++

  • 算法题

  • 存储系统

    • 导航

    • 单机IO基础

    • IO语义与持久化

    • 块层与高速路径

      • 块层与I-O调度总览
      • blk-mq多队列模型
        • 单队列模型的问题从哪里来
        • blk-mq 把路径拆成两层队列
        • request 在多队列中如何推进
        • I/O 调度器没有消失
        • 为什么它和 NVMe 特别匹配
        • 性能分析视角如何变化
        • 和高速存储路径的关系
        • 进一步讨论
      • 高速存储路径总览
    • Ceph

    • DAOS

    • 归档

  • CephFS

  • 分布式系统

  • 计算机网络

  • Redis与缓存

  • Kubernetes

  • 技术笔记
  • 存储系统
  • 块层与高速路径
lisheng
2026-04-27
目录

blk-mq多队列模型

# blk-mq多队列模型

如果说 块层与I-O调度总览 解释的是 Linux 为什么需要一个统一的块层,那么 blk-mq 要解释的是另一个阶段的问题:当 CPU 核数、线程并发和存储设备队列能力都上来以后,块层本身怎样避免成为新的串行瓶颈。

这篇文章不展开每个内核结构体,也不把 blk-mq 简化成“NVMe 专用加速机制”。它的主线是:传统块层把大量请求压到少数共享队列上时,锁、缓存行迁移和提交路径串行化会逐渐压过设备本身;blk-mq 则通过把 CPU 侧提交、软件队列、硬件派发队列和完成路径重新组织起来,让块层更适合多核与多队列设备。

# 单队列模型的问题从哪里来

在机械硬盘和低并发设备占主导时,块层把请求汇聚到一条或少数请求队列上并不是坏事。请求进入队列后,内核可以做合并、排序和调度,用顺序化访问减少寻道成本。此时设备慢、CPU 核数少,软件队列上的锁竞争通常不是主要矛盾。

SSD,尤其是 NVMe,把这个前提改掉了。设备本身可以同时处理大量命令,延迟也比机械硬盘低很多。与此同时,多核 CPU 上可能有许多线程同时提交 I/O。如果这些请求仍然集中争用同一个请求队列,块层的软件路径就会出现新的热点:队列锁竞争变重,多个 CPU 反复修改同一批缓存行,提交和完成路径跨核抖动,设备明明有并行能力,却被内核队列组织方式挡住。

因此,blk-mq 的出现不是为了让设备“天然更快”,而是为了让软件栈不要在高并发设备前面先堵住。它解决的是扩展性问题:当请求来源和设备能力都并行化以后,块层也必须从单中心队列模型转向多队列模型。

# blk-mq 把路径拆成两层队列

blk-mq 的核心思路,是把 CPU 侧提交并发和设备侧执行并发分开建模,再通过映射关系把它们接起来。它通常包含两类队列:软件队列和硬件队列。

软件队列更靠近 CPU。提交 I/O 的 CPU 会优先把请求放到与自己更接近的软件上下文中,减少所有 CPU 争同一把锁的概率,也减少缓存行在 CPU 之间来回迁移。这里关注的是提交路径的本地性:哪个 CPU 产生请求,最好就让它在较短路径内完成请求构造和入队。

硬件队列更靠近设备。它代表块层向设备驱动派发请求的并发通道,可以映射到设备真正支持的 submission queue,也可以由驱动根据设备能力进一步组织。这里关注的是设备侧并发:设备能同时接收多少请求、每条队列深度是多少、完成中断或轮询如何回到 CPU。

这两层队列之间不是简单的一一对应关系。软件队列数量通常和 CPU 拓扑、提交上下文有关,硬件队列数量则受设备、驱动和配置影响。blk-mq 真正要处理的,是怎样把多个 CPU 的请求合理分流到有限的硬件派发通道上,同时避免某些队列过热、某些队列闲置。

# request 在多队列中如何推进

从上层文件系统或 Page Cache 下来的 I/O,进入块层后会被表达成 bio,再进一步组织成可提交给设备的 request。在 blk-mq 中,请求不会简单地进入一个全局大队列,而是沿着当前 CPU 对应的提交上下文进入软件队列,再根据映射关系被放到目标硬件队列,最后由驱动提交给设备。

这个过程改变了性能分析的观察点。过去更容易把注意力放在单个请求队列里的排序、合并和调度策略上;到了多队列模型,还必须关注请求从哪个 CPU 提交、进入哪个软件队列、映射到哪个硬件队列、在哪个 CPU 上完成,以及完成路径是否和提交路径保持了足够好的亲和性。

完成路径同样重要。设备完成一个请求后,通常会通过中断或轮询让内核知道结果。完成处理如果总是落到远离提交 CPU 的核心上,就可能引入跨核唤醒、缓存失效和尾延迟抖动。对于 NVMe 这类高并发低延迟设备,完成路径的 CPU 分布有时和提交路径一样关键。

# I/O 调度器没有消失

blk-mq 并没有取消调度问题,只是改变了调度问题所在的位置。传统块层更像是在一个集中队列里讨论请求如何排序;多队列块层则要同时考虑每条队列内部怎样调度,以及多条队列之间怎样保持负载、亲和性和设备能力的平衡。

这也是为什么 blk-mq 时代仍然有调度器,只是它们面对的约束已经变了。对旋转磁盘而言,顺序性和合并仍然很重要;对 SSD 和 NVMe 而言,过度排序可能不如保持并发、降低软件路径开销和减少尾延迟重要。调度器不再只是“把请求排好队”,还要在多队列结构中决定什么时候合并、什么时候派发、怎样避免某些上下文长时间占用队列资源。

因此,看 blk-mq 不能把它和 I/O 调度器拆成两个互不相关的话题。blk-mq 提供的是多队列框架,调度器是在这个框架内处理公平性、合并、延迟和吞吐取舍。设备越快,调度器自身的成本越容易变成问题;请求来源越复杂,完全不调度又可能带来抖动和饥饿。

# 为什么它和 NVMe 特别匹配

NVMe 从协议和设备设计上就不是单队列思路。它支持多条 submission queue 和 completion queue,主机可以让不同 CPU 或不同提交路径使用不同队列,把命令并行送到设备侧。这样的设备如果挂在传统单队列块层下面,硬件能力很容易被软件队列串行化抵消。

blk-mq 和 NVMe 的匹配点就在这里:Linux 块层用软件队列和硬件队列表达主机侧并发,NVMe 驱动再把硬件队列映射到设备队列能力上。这样,多个 CPU 可以更自然地并发提交 I/O,设备完成请求后也可以通过更合理的中断亲和或轮询路径回到对应 CPU。

但这不意味着 blk-mq 只服务于 NVMe。SCSI、virtio-blk、云盘和其他块设备也可以受益于多队列框架。只是 NVMe 把这种收益放大得最明显,因为它同时具备低延迟、高队列深度和原生多队列能力。

# 性能分析视角如何变化

在 blk-mq 之前,分析块层性能时常见问题是:请求有没有合并,调度器是否适合,顺序和随机访问差异多大,设备是否已经打满。到了 blk-mq 之后,这些问题仍然存在,但不够了。多队列模型要求把整条并发路径也纳入观察。

一个高并发 I/O 问题可能不是设备慢,而是某些 CPU 过度集中提交;也可能不是队列深度不够,而是硬件队列映射不均;还可能不是平均吞吐问题,而是完成中断落在不合适的 CPU 上,导致尾延迟上升。对于虚拟化、容器和分布式存储系统,问题还会继续叠加:上层请求来源、网络线程、存储线程、块层队列和设备中断可能分别落在不同 CPU 上,亲和性一旦错位,延迟会在多个边界累积。

所以,blk-mq 带来的不是一个单独调优旋钮,而是一种新的观察方式:请求不是“进入块层然后交给设备”这么简单,而是沿着 CPU、本地提交上下文、软件队列、硬件队列、驱动、设备队列和完成 CPU 形成一条并发链。链上任意一段失衡,都可能让高性能设备表现得像被软件栈卡住。

# 和高速存储路径的关系

理解 blk-mq 之后,后面的 高速存储路径总览 才更容易串起来。Direct I/O 试图绕过 Page Cache 的缓存语义,但它仍然可能进入块层;io_uring 降低提交和完成的系统调用开销,但底层请求仍然要被组织到块设备队列;SPDK 更进一步,把 NVMe 访问搬到用户态轮询路径中,本质上是在绕开内核块层的一部分软件开销。

这些路径的共同背景是:设备已经足够快,传统内核路径中的系统调用、锁、调度、中断和跨核成本都变得可见。blk-mq 是 Linux 内核块层对这个背景的一次结构性适配;Direct I/O、io_uring 和 SPDK 则是在不同边界上继续减少数据路径和控制路径成本。

放到分布式存储里看,这个关系更明显。Ceph、DAOS 或数据库系统上层可能已经有网络并发、对象并发和用户态线程池,如果落到本地 NVMe 时又被块层队列、CPU 亲和或完成路径卡住,尾延迟会被放大。blk-mq 不是分布式存储性能的全部答案,但它是本地设备路径能否跟上上层并发的关键背景。

# 进一步讨论

【待展开:software queue 与 hardware queue 的映射关系|software-vs-hardware-queue】:多队列模型的关键不只是队列数量,而是 CPU 侧提交上下文如何映射到设备侧派发能力。

【待展开:CPU 亲和、中断亲和与队列绑定|cpu-interrupt-affinity-queue-binding】:高并发块设备路径里的尾延迟,经常来自提交 CPU、完成 CPU 和设备队列之间的错位。

【待展开:blk-mq 与 NVMe 队列模型的关系|blk-mq-nvme-queue-model】:需要把 Linux 块层硬件队列和 NVMe submission/completion queue 的对应关系单独拆开。

Edit (opens new window)
Last Updated: 2026/04/27, 14:16:46
块层与I-O调度总览
高速存储路径总览

← 块层与I-O调度总览 高速存储路径总览→

最近更新
01
待完成专题池
04-28
02
待完成专题池
04-28
03
为什么 Kubernetes CSI 插件架构要拆成 Controller、Node 与 Sidecar
04-28
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式