土法炼钢 · 系统与基础设施

【SPDK 用户态存储】bdev 模块边界:nvme / aio / malloc 与何时不该叠层

文章导航

分类入口
storagelinux
标签入口
#spdk#bdev#nvme#aio#raid#lvol#stacking#v26.01

目录

第 6 篇 把 bdev 核心钉成统一提交与排队契约。真正决定延迟形状与失败模式的,是挂在契约之下的 模块:有的直接碰硬件或内核,有的只在已有 bdev 上再虚拟一层。官方 Block Device User Guide 列出的模块远多于生产常用集合;本文只划 边界与选型轴,不做成 RPC 百科。

本文是「SPDK / 用户态存储栈」系列第 7 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 6 篇 · bdev 核心 统一接口与生命周期
第 7 篇 · bdev 模块 后端 / 虚拟层边界与叠层代价
第 8 篇 · NVMe-oF target namespace 映射到 bdev

上一篇bdev 核心 · 下一篇NVMe-oF target

版本锚定:SPDK v26.01Block Device User Guide 中 nvme / aio / malloc / null / raid / lvol 等章节;源码 module/bdev/ @ v26.01。无伪造基准测试。

本篇不展开:crypto / OCF / RBD / DAOS 等可选模块的完整配置;blobstore 内部实现;把结果导出成内核 NBD 的运维菜谱。


一、两类模块:后端 vs 虚拟层

官方区分 bdev module(对接某类存储)与 vbdev module(在已有 bdev 上再创建块设备)。排障时先问:出错的名字是 叶子后端,还是中间的虚拟层?

flowchart TB
  subgraph leaf ["Leaf backends"]
    nvme["bdev_nvme"]
    aio["bdev_aio"]
    uring["bdev_uring"]
    malloc["bdev_malloc"]
    null["bdev_null"]
  end
  subgraph virt ["Virtual layers"]
    raid["bdev_raid"]
    lvol["bdev_lvol"]
    gpt["bdev_gpt"]
    pass["passthru / delay / ..."]
  end
  front["nvmf / vhost / app"] --> virt
  virt --> leaf
  front --> leaf
角色 代表模块 一句话边界
用户态 NVMe 叶子 bdev_nvme PCIe 或 NVMe-oF 控制器 attach 后导出 namespace bdev
内核旁路叶子 bdev_aio / bdev_uring 经 Linux AIO 或 io_uring 访问内核块设备 / 文件(O_DIRECT
测试叶子 malloc / null 大页 ramdisk;或丢弃写、读未定义数据的空靶
虚拟聚合 raid 多成员拼 RAID0 / 1 / 5F / concat
虚拟空间管理 lvol 在 lvstore(基于 blobstore)上切逻辑卷
自动分区 gpt 识别 SPDK GPT 分区类型并导出分区 bdev

前端(尤其 nvmf_subsystem_add_ns)只认 最终要导出的那个 bdev 名。叶子与虚拟层可以叠,但每多一跳就多一处队列、完成回调与错误翻译。


二、叶子模块:保证什么、不保证什么

2.1 bdev_nvme

2.2 bdev_aiobdev_uring

官方对 AIO 模块的定位很明确:通过 Linux AIO 访问内核块设备或文件,且使用 O_DIRECT,绕过页缓存;并写明这是「在不用用户态驱动的前提下,用户态 target 最接近典型内核 target 的模式之一」。

v26.01 changelog 对 bdev_aio 注明:默认不再使用 RWF_NOWAIT,需显式 --no-wait。版本相关行为以 changelog 为准,避免从旧博客抄默认值。

2.3 mallocnull

叶子选型还有一条常被忽略的轴:失败是否可归因到硬件。 bdev_nvme 的完成错误往往能对照 NVMe status;aio/uring 则混有内核 errno 与块层语义;malloc/null 几乎不教你「盘坏了怎么办」。若目标是练排障剧本,应用真实 nvme 叶子(或至少 aio 指向真实设备),而不是长期停留在 malloc。


三、虚拟层:raid 与 lvol

3.1 RAID

User Guide:RAID vbdev 可把任意 SPDK bdev 组合成一个 RAID bdev;当前支持 RAID0、Concat、RAID1、RAID5F(RAID5F 需 --with-raid5f)。带冗余级别支持降级与 rebuild;可选把 metadata 落在成员盘上,避免进程重启后手工重建。

边界:

3.2 lvol

逻辑卷建立在 lvol store 之上,而 lvol store 又建在某个 base bdev 上,底层是 Blobstore(官方指向 Logical volume / Blobstore 文档)。RPC 顺序是:bdev_lvol_create_lvstorebdev_lvol_create

边界:

GPT 模块则更「被动」:识别特定 partition type GUID 后自动导出分区 bdev;改分区表常需经 NBD 暴露给内核工具再重新 attach——细节见官方 GPT 节,本篇不展开菜谱。

对「要不要上 lvol」可以用更硬的产品问题代替性能测试口号:是否需要在同一块物理介质上切出多个可独立扩缩的卷、是否需要快照类能力(若走 blobstore/lvol 生态)、以及元数据损坏时的恢复演练是否已写进 runbook。若三个答案都是「暂时不需要」,整盘 bdev_nvme 直出通常更干净。RAID 同理:先回答冗余与条带是否在 本进程的用户态栈 解决,还是应在上层集群/客户端解决;把集群级复制再叠一层 RAID1,常常只是重复交税。


四、何时不该叠层

叠层伤害来自三类,而不是「虚拟层原罪」:

  1. 额外跳数:每次 vbdev 完成回调多一跳;crypto 类写路径还可能引入临时缓冲(官方 crypto 节说明写用 scratch,避免污染源缓冲)。无证据时不要编造微秒数,但要承认跳数与拷贝是真实成本来源。
  2. 错误语义变长:叶子返回的介质错误,经 RAID/lvol 翻译后,上层看到的是另一套状态;排障必须从导出 bdev 名 向下展开依赖树
  3. 资源与重建策略冲突:RAID rebuild、lvol 元数据写、远端 NVMe-oF 重传同时抢同一组核与网卡时,轮询栈没有内核 elevator 帮你「公平」。

实用排除:

目标 更稳的形状 慎用
最低延迟本机盘 bdev_nvme 直出 null/malloc 当「假盘」却当生产结论
快速验证 nvmf malloc → subsystem NS 未测完就上 RAID5F+lvol+crypto
盘仍在内核 aio/uring 叶子 再叠多层 vbdev「顺便」做完整存储产品
多租户切分 单层 lvol 或单层 RAID lvol 上再套 RAID 再套 crypto 却无观测

争论:存储产品倾向「功能完备的虚拟块栈」;数据面倾向「能少一层是一层」。SPDK 同时提供两边工具,默认并不替你做产品裁剪。本系列立场:前端需要的能力用 最少虚拟层 表达;把「将来可能要的功能」提前叠进热路径,是常见过度设计。


五、配置与依赖:先画树,再写 RPC

模块边界一旦清楚,配置顺序就变成依赖图问题,而不是「RPC 清单抄写」:

  1. 先创建叶子(attach NVMe、create malloc/aio),确认 bdev_get_bdevs 一类枚举里名字存在;
  2. 再挂需要全员到齐的虚拟层(RAID 可延迟 claim,但导出前必须成员齐;lvol 必须先有 lvstore);
  3. 最后把顶层 bdev 名交给前端nvmf_subsystem_add_ns、vhost 等)。

反例:先 add_ns 再创建底层、或把 crypto 建在即将被 RAID 替换的临时名上,会导致前端持有陈旧名字、热重启后映射漂移。第 12 篇谈 JSON-RPC 生命周期时会回到「配置与数据面谁先谁后」;本篇只要求:每一条 RPC 都对应依赖树上的一条边

passthru / delay 这类模块适合当教学与注入延迟的靶,不适合出现在生产热路径叙事里。官方写明 delay 不是高保真盘模拟,而是「看额外延迟如何冲击应用」的粗粒度工具;null 则是「测栈开销上界」。把它们留在实验台账,避免写进架构图冒充介质层。

可选模块(RBD、OCF、DAOS、xNVMe、virtio 发起端等)各自引入外部运行时或许可边界:例如 RBD 要权衡「每 bdev 一个 cluster 连接」与「共享 cluster 对象」的线程模型(官方 User Guide 明确点出 librados 线程与上下文切换张力)。它们证明 bdev 插件面足够宽,但 宽不等于你的产品默认该全开。本系列主干仍是 nvme 叶子 ± 必要时一层虚拟层 → nvmf/vhost。

5.1 一张「允许的形状」备忘

形状 何时合理 主要代价
nvme → nvmf NS 本机用户态盘导出 设备独占、绑盘运维
aio/uring → nvmf NS 盘留在内核,只要用户态 target 完成模型混杂、延迟谱不同
malloc → nvmf NS 联调、CI 无持久性
nvmelvol → nvmf 多租户切分 元数据与空间耗尽
nvme×N → raid → nvmf 进程内需要冗余/条带 重建与降级吃资源
nvmeraidlvolcrypto → nvmf 极少作为默认;需强产品理由 跳数、错误翻译、观测盲区叠加

表的最后一行不是「禁止」,而是 默认拒绝,除非依赖图、观测与故障演练齐备。第 8 篇开始的 nvmf 配置,应默认从上表前几行长出来。


六、开放问题

  1. ZNS / 分区命名空间在通用 bdev + RAID/lvol 上的组合语义仍不完整:条带与 zone 约束如何共存,缺少与经典块 RAID 同级的社区共识实现。
  2. 内核旁路叶子(aio/uring)与用户态 NVMe 叶子混部时,统一 QoS 与超时模型缺失——第 15 篇会对照,但生产策略仍靠部署约定。
  3. vbdev 可观测性:第 13 篇的 iostat 若只看顶层 bdev,会吞掉下层重建与元数据写;如何默认导出依赖图仍是工程缺口。
  4. RAID 元数据落盘默认关闭(官方为兼容性):运维自动化若假设「进程重启后阵列自愈」,会与默认行为冲突——产品是否强制开启 metadata,应写成显式策略而非口头约定。

七、参考资料

规范 / 官方文档(A)

源码(A)

站内

实验台账


八、小结

  1. 先分叶子与虚拟层;导出给 nvmf/vhost 的是最终 bdev 名。
  2. nvme / aio·uring / malloc·null 覆盖「用户态盘、内核旁路、测试靶」三条叶子轴;raid / lvol 解决聚合与切分,不是默认必叠。
  3. 叠层伤害来自跳数、错误翻译与资源策略冲突;用最少层表达产品能力。
  4. 把选好的 bdev 挂进 NVMe-oF——见 第 8 篇

系列目录 · 上一篇:bdev 核心 · 下一篇:NVMe-oF target

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .