选型争论若滑成「谁更快」,会立刻要一张没有口径的延迟榜。本系列前 14 篇给出的判决材料其实是另一类:一次 I/O 走哪条栈、失败落在哪、运维独占什么。
本文是第 15 篇:对照三条常见本机路径——SPDK
用户态、内核 NVMe
块层、O_DIRECT +
io_uring——并写共置与迁移成本。数字结论留给你的硬件与
第 13 篇
的合法口径;本站不发明排名。
本文是「SPDK / 用户态存储栈」系列第 15 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 14 篇 · 排障坐标系 五轴清单 第 15 篇 · 对照内核路径 机制差、共置、迁移税 第 16 篇 · 选型收束 排除树与开放问题
版本锚定:SPDK v26.01。内核路径细节外链 storage/79、io_uring 系列、storage/03;不重写内核全书。
一、三条路径各保证什么
flowchart TB
subgraph spdk_path ["SPDK userspace"]
app1["App / nvmf / vhost"] --> reac["reactor poll"]
reac --> ubdev["bdev"]
ubdev --> unvme["userspace NVMe"]
end
subgraph kernel_nvme ["Kernel NVMe"]
app2["App"] --> vfs1["VFS / block"]
vfs1 --> knvme["nvme driver"]
end
subgraph direct_uring ["O_DIRECT + io_uring"]
app3["App"] --> uring["io_uring SQ/CQ"]
uring --> vfs2["VFS + block"]
vfs2 --> knvme2["nvme driver"]
end
| 维度 | SPDK 用户态 | 内核 NVMe(常规定稿) | O_DIRECT + io_uring |
|---|---|---|---|
| 完成模型 | 用户态轮询为主 | 中断 / 内核轮询等内核机制 | 完成通知进 CQ;仍经内核块层 |
| 设备占用 | VFIO/UIO 独占常见 | 内核驱动管理,易与系统共置 | 同左(内核驱动) |
| 块抽象 | bdev(砍/改了内核 block 诸多策略) | Linux block / mq | 同内核块层 |
| 典型职责 | 存储目标、用户态栈、极致轮询延迟敏感面 | 通用 OS 存储 | 应用自管缓存时的高 IOPS 提交 |
| 运维方言 | JSON-RPC、reactor、setup.sh | sysfs、nvme-cli、块层 QoS |
io_uring 参数 + Direct 对齐 |
站内 79
已钉:O_DIRECT 解决缓存语义,io_uring
解决提交效率——二者不替代用户态驱动。SPDK
解决的是「驱动与块栈是否搬出内核」,不是「要不要
Direct」。
判决句:
- 需要 NVMe-oF target / vhost 用户态后端 / 与 DPDK 系同一套轮询运行时 → SPDK 进候选。
- 需要 与 OS 共置、用标准块设备与生态工具 → 内核 NVMe。
- 需要 应用自管缓存 + 降
syscall,且接受内核块层 →
O_DIRECT+io_uring(先读 79 与 生产翻车)。
二、共置:同机两条栈的真实约束
「一台机器上数据库走 io_uring,网关走 SPDK」听起来美好,硬约束是 设备与 CPU:
- 同一 NVMe 不能同时被内核
nvme与 SPDK vfio 友好共享(常规独占模型)。共置通常意味着:不同盘 分给不同栈,或接受卸载/重绑的切换窗。 - CPU:SPDK reactor 吃满专用核;io_uring 应用也要算力。共置时要显式切分 cpumask,否则互相偷时间,却在各自监控里都「很忙」。
- 大页与 IOMMU:SPDK 环境脚本会影响整机;容器/kube 设备插件策略要写进同一运维手册。
- 排障坐标加倍:第 14 篇清单必须标明流量进了哪条栈——否则「磁盘超时」会同时误导两边 on-call。
共置合法场景举例(机制层,非推荐清单):系统盘与日志盘留内核;少数数据盘交给 SPDK 做 nvmf target;管理面 SSH 与监控仍走内核网卡。非法幻想:同一命名空间块设备「有时 SPDK 有时内核」而无编排切换。
三、迁移税:从内核路径迁到 SPDK(或反向)
| 税项 | 内核 → SPDK | SPDK → 内核 |
|---|---|---|
| 设备 | bind vfio,应用改连 bdev/RPC/nvmf | unbind、恢复 nvme,改回块设备路径 |
| 配置真相 | 学会 save_config / -c 与热 RPC
纪律 |
回到 fstab/LVM/多路径传统工具 |
| 观测 | iostat RPC 方言 | iostat/blktrace/nvme 日志 |
| 性能门禁 | 按第 13 篇自建口径;官方 report 仅方法参考 | 同理,勿沿用用户态 report 数字 |
| 人员 | 轮询 CPU 税与「高占用即正常」的认知切换 | 重新接受中断/块层语义 |
迁移失败模式常见于:只迁数据面、不迁配置库存与排障手册。进程一重启,RPC 热配置蒸发(第 12 篇),看起来像「SPDK 不稳」,其实是生命周期模型没迁完。
反向迁移(SPDK → 内核)往往发生在:付不起专用核、要与云主机盘/快照生态合流、或团队编制撑不住用户态运行时。机制上这不是「认输」,而是排除树下一格(第 16 篇)。
四、和协议层、异步模型层的分工
| 问题 | 去哪读 |
|---|---|
| NVMe/NVMe-oF 命令与队列语义 | storage/03 |
O_DIRECT 对齐与 io_uring 固定缓冲 |
storage/79 |
| SQ/CQ、epoll 对照 | io_uring、os/57 |
| 用户态栈内延迟落点 | 本系列 02–10、14 |
本篇不比较「论文里用户态一定赢」——硬件代际、轮询核税、尾延迟与运维成本会改写结论。开放争论留给终章。
五、何时「看起来该上 SPDK」其实不该
下列动机经常把团队带进用户态,却经不起第 14 篇清单与排除树拷问:
- 只看到官方 Performance Report 的峰值曲线,没看到专用核与硬件代际——把外部方法抄成本机 SLO。
- 本机数据库盘已经在
O_DIRECT+io_uring 上可归因,却为了「技术先进」改绑 vfio,结果失去标准块工具链。 - 云主机盘 / 托管块本就不能 VFIO 独占,却按裸金属 SPDK runbook 推进,最终卡在环境轴。
- 想顺手做 NVMe-oF,但流量模型其实是同机容器互访——可能 vhost 或内核路径更贴,却先上了织物传输复杂度。
- 编制上无人维护 JSON 库存,把热 RPC 当长期配置,第一次滚动重启即漂。
对称地,下列信号说明内核路径可能正在成为瓶颈,值得认真评估 SPDK(仍不是自动切换):
- 已确认瓶颈在内核块层/驱动路径,且应用愿改对接方式(bdev、nvmf、或嵌入 SPDK 库)。
- 产品形态本身就是存储目标端或用户态 vhost 后端。
- 与现有 DPDK/轮询运行时共进程,能摊销核税与环境成本。
评估动作应是:用第 13 篇口径在目标硬件上做可复现实验,并写下迁移税清单(第 15 篇第三节)是否有人签字买单——而不是先改生产绑盘。
六、谱系与工程间隙
谱系(简):NVMe 多队列把并行提交变成硬件常识 → 内核块层用 mq 与中断/轮询变体服务通用 OS → io_uring 把「完成通知」做进高效系统调用界面 → SPDK 把驱动与块抽象整体搬到用户态 poll。四者解决的痛点不同:硬件队列、OS 共用、syscall 摊销、绕过内核数据面。
工程间隙:论文或博客里的「用户态更短路径」常假设专用核、巨页、无共置干扰、且忽略控制面与升级。生产里这四项经常不同时成立。另一间隙是工具链:内核路径享受
blktrace、发行版多路径、云快照;SPDK 路径享受
RPC 热改与 poll
延迟,但快照/迁移/监控要自建。选型比较的是间隙能否被编制填上,不是形容词。
迁移演练应按「可回滚」设计:保留一张未绑定的对照盘走内核路径,先将只读或可丢流量切到 SPDK,再扩写流量。回滚剧本必须包含驱动 rebind 与库存停用,而不是假设「停掉 SPDK 进程盘就会自动回来」——设备归属不会因为进程退出就自动回到你期望的内核驱动状态,除非环境脚本明确做了对称的 teardown。把 teardown 与 setup 放在同一仓库、同一评审流,是迁移税里最容易被跳过却最贵的一项。
对应用改造成本也要单独立项:从打开内核块设备到对接 SPDK 库或改走 NVMe-oF,中间差的是整条 I/O 提交与完成语义。有的团队用 SPDK 只做 target、应用仍用内核 initiator,这是降低改造面的合法折中,但要把网络与传输轴重新纳入第 14 篇清单,不能假装「还是本地盘」。
容器与编排环境下,共置约束更严:设备插件、CPU manager、大页与 IOMMU 都必须同时说真话。只把 SPDK 进程塞进容器、却仍让 kubelet 与其他 Guaranteed pod 争抢同核,会得到「容器化成功、延迟目标失败」的结果。对照 storage/79 的路径时,记住 io_uring 应用也可以在同一节点争用 CPU——共置是资源账,不是只有 SPDK 才有的税。
最后,监管与合规场景可能强制「标准块设备可见」或特定加密/审计链路只认内核路径。此时机制优势让位于合规约束,排除树应在第一叶就走开。把合规当成事后补丁,只会在上线前夕把用户态栈整段拆掉。
从成本会计角度看,SPDK 的核税应计入「存储数据面专用容量」,不要从通用计算池偷核却仍按通用池报利用率。内核路径的中断与软中断占用则常被摊进节点共用账本,两种会计口径不同,直接比「CPU%」会得出相反结论。做选型材料时,建议同时报:有效带宽或 IOPS、专用核数、以及是否独占设备——缺一项就标「不可比」。
若业务同时需要快照、克隆、薄置备等企业阵列功能,要问这些功能落在哪一层:SPDK bdev 模块、上层分布式存储、还是云控制面。功能缺口不能用「用户态更快」填补;应回到排除树看是否该留在内核/云块生态。
教育成本也是迁移税:让习惯块层的工程师接受「CPU 100% 可能健康」,以及让习惯轮询的工程师接受「内核路径的可观测方言」,都需要演练预算。忽略这项会在排障时发生方言冲突,延长故障时间。
也可以把三条路径理解成不同的「所有权边界」:内核路径所有权在 OS 与发行版工具链;io_uring 组合路径所有权在应用与内核异步接口;SPDK 路径所有权在用户态进程与 RPC 库存。所有权不清时,SLA 与值班表都会悬空。
所有权边界写进值班表后,才能把五轴清单里的「找谁」从口头约定变成可执行路由。
值班路由不清时,再完整的机制对照表也只会停在文档里。
七、参考资料
官方与站内(A/B)
- SPDK 文档总览与 v26.01 机制篇(本系列 01–14)。
- storage/79、io_uring 系列、storage/03。
工程判断(标明)
- 共置与迁移税表:来自设备独占与 RPC 生命周期的机制推导,非基准测试结论。
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【SPDK 用户态存储】选型收束:机制排除树与系列开放问题
用机制排除树收束 SPDK 相对内核 NVMe 与 O_DIRECT+io_uring 的选型;回收阅读路径,并列出 ZNS/KV、DPU 卸载、与 Ceph OSD 用户态边界等开放问题;不做延迟排行榜。
【SPDK 用户态存储】用户态存储全景:从 Reactor 到 NVMe-oF 的坐标系
定位 SPDK 相对内核块层、O_DIRECT+io_uring 与 NVMe 协议百科的生态位;钉住 I/O 路径、线程模型、设备独占、块抽象、远端传输五条坐标系,并给出 16 篇阅读路线。
【SPDK 用户态存储】用户态 NVMe 驱动:Controller、Qpair 与 Poll Group
拆解 SPDK 被动式 NVMe 库:probe/attach、admin 与 I/O qpair、完成轮询与 poll group;说明单线程独占 qpair、零拷贝提交路径及相对内核驱动的边界。
【SPDK 用户态存储】bdev 模块边界:nvme / aio / malloc 与何时不该叠层
按后端与虚拟层两类划清 SPDK bdev 模块边界:nvme、aio、malloc、null、raid、lvol 各自保证什么;说明叠层何时伤害延迟与排障,并拒绝做成模块百科。