第 8 篇 把 subsystem 与 NS→bdev 钉住之后,延迟与 CPU 占用很大一块落在 transport:同样是 NVMe 胶囊,走 RDMA verbs 还是 TCP 字节流,完成通知靠狂轮询还是事件唤醒,成本模型完全不同。本文只回答:两种主传输差在哪、默认为何轮询、v26.01 把 RDMA interrupt-driven 写进 changelog 之后何时该开。
本文是「SPDK / 用户态存储栈」系列第 9 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 8 篇 · NVMe-oF target subsystem 与 NS 映射 第 9 篇 · 传输层 RDMA / TCP;轮询 vs interrupt 第 10 篇 · Initiator host 路径与内核互通 上一篇:NVMe-oF target · 下一篇:Initiator 与互通
版本锚定:SPDK v26.01 LTS;NVMe over Fabrics Target、NVMe-oF Interrupt Mode、NVMe-oF Target Programming Guide(RDMA 节);Changelog v26.01 nvmf 条。无跨传输延迟排行榜。
本篇不展开:FC 厂商 LLD、uring 套接字实现细节、完整 TLS 握手菜谱。
一、传输在 target 中的插件位置
NVMe-oF 规范把「队列与命令」和「底下那根管子」分开;SPDK
用 spdk_nvmf_transport
插件镜像这一点(Programming Guide)。同一
spdk_nvmf_tgt 可同时挂多种传输;subsystem 的
listener 选择具体 trtype。
构建差异(Getting Started):
- TCP:默认编进
nvmf_tgt,无额外特殊库依赖(相对 RDMA)。 - RDMA:需 OFED/verbs
用户态栈;
scripts/pkgdep.sh --rdma,./configure --with-rdma。 - FC:额外厂商 LLD,不在本篇范围。
RPC 上先
nvmf_create_transport -t RDMA|TCP ...,再在
nvmf_subsystem_add_listener
里引用对应传输与地址。传输参数(I/O unit、max
I/O、in-capsule data、max qpairs
等)影响胶囊与缓冲布局,不是「设了就保证更低延迟」的旋钮——改之前先理解默认是否已匹配网卡与
workload。
二、RDMA vs TCP:机制差,不是口号差
2.1 RDMA
Programming Guide:
- 实现基于 libibverbs + rdmacm,不走 DPDK 用户态 RDMA 驱动栈。
- 为扩展连接数:每个 poll group 共享一个 RDMA completion queue,qpair 自有收发队列但共用该 CQ,从而一次 poll 扫一组完成。
- 请求走显式状态机;并处理 SEND/RECV 与 READ/WRITE
不同队列深度、RECV 与 send
完成可能乱序到达等边角(
rdma_recv/rdma_request拆分)。 - 零拷贝叙事:NIC ↔︎ 主机内存 ↔︎ SSD,避免主机内存内二次拷贝。
Getting Started 另列运维限制:RDMA NIC
内存区域注册数量有限,动态巨页过多时可能耗尽
MR,表现为 DMA 内存分配失败;可用 1G 巨页或启动时
--mem-size/-s
预留,把预留内存注册成单区域。E810 RoCE 等平台还有 qpair
销毁/工作请求刷不干净导致进程难退出的已知问题——属平台注意事项,不是「RDMA
一定更稳」。
前置模块加载(ib_uverbs、rdma_ucm
等)与网卡 IP
配置是传输层「能不能起来」的硬门槛;应用日志里的 nvmf
错误,有时只是 verbs
栈没就绪的下游表现。联调顺序应是:内核模块与地址 →
nvmf_create_transport → listener,而不是先怀疑
bdev。
2.2 TCP
- 部署面通常更宽:普通以太网与现有 IP 运维即可,无需 InfiniBand/RoCE 专有栈。
- 数据路径是套接字语义上的胶囊封装;是否出现额外拷贝取决于实现与配置,不能自动套用 RDMA 零拷贝句子。
- 与内核
nvme-tcp/nvmet-tcp互通是官方主线测试组合之一(Getting Started 总述)。
2.3 怎么选(工程判断,无未测倍率)
| 维度 | 更倾向 RDMA | 更倾向 TCP |
|---|---|---|
| 网络 | 已有 RoCE/IB,verbs 运维成熟 | 标准 IP 网络,希望少依赖 |
| 拷贝与 CPU | 需要官方所述零拷贝路径 | 可接受套接字路径复杂度换部署简单 |
| 故障面 | 要处理 MR 上限、厂商 RoCE 边角 | 要处理拥塞、重传、中间盒对长连接的影响 |
| 安全特性入口 | 视部署;带内认证等按规范与 SPDK 支持矩阵 | TLS(文档标 experimental)等挂在 TCP 上 |
争论:存储网络「必须 RDMA」的营销叙事,vs 云与超融合里 NVMe/TCP 以可运维性取胜。SPDK 同时实现两者,并把选择留给 listener;本系列立场:先匹配网络与运维能力,再谈轮询/中断——传输选错时,interrupt mode 救不了错误的拓扑。
flowchart TD
q["Need NVMe-oF export"] --> net{"Fabric ready?"}
net -->|"RoCE/IB + verbs"| rdma["RDMA transport"]
net -->|"IP only / simple ops"| tcp["TCP transport"]
rdma --> load{"Sustained high IOPS?"}
tcp --> load
load -->|yes| poll["Default polling reactors"]
load -->|no / idle tax hurts| intr["Consider --interrupt-mode"]
三、默认模型:轮询
SPDK 数据面默认假设:reactor 上的 poller 持续询问传输与 bdev 是否有完成。这对高队列深度、高 IOPS 有利——避免中断与上下文切换——但在 低负载或空闲 时表现为 CPU 空转税(系列第 2、14 篇会反复碰到)。
对 nvmf:
- 每个 poll group 在其线程上轮询已分配 qpair;
- acceptor / 连接管理也有对应 poller;
- 核被
-mmask 钉住后,空闲也占着逻辑核。
因此「target CPU 很高但网卡 pps 很低」优先怀疑 轮询空转,而不是先怀疑 bdev 介质。
四、v26.01:RDMA
interrupt-driven 与 --interrupt-mode
4.1 Changelog 原句锚点
SPDK Changelog v26.01 · nvmf:
Added interrupt-driven mode support for NVMe-oF RDMA transport to reduce CPU polling overhead and improve efficiency under low-load conditions, while preserving the existing polling model for high-throughput workloads.
同一版本还写明 target 侧更广的 interrupt 能力,并与全局应用参数衔接;官方 NVMe-oF Interrupt Mode 文档归纳:
- target 支持 vfio-user、TCP、RDMA 的 interrupt mode;
- 空闲时 reactor 可睡,网络/设备事件再唤醒,从而降低低负载 CPU;
- 应用侧:
nvmf_tgt --interrupt-mode(或库 APIspdk_interrupt_mode_enable()/opts->interrupt_mode)。
这与「只改 RDMA」的误解要分开:changelog 特别点名 RDMA 新增 interrupt-driven;完整运维手册则覆盖 TCP/RDMA/vfio-user,并列出限制。
4.2 使用与验证(官方 checklist 语义)
NVMe-oF Interrupt Mode:
- 仅 Linux;传输需提供用户态唤醒源——RDMA 上 I/O 完成经 completion channel fd。
build/bin/nvmf_tgt --interrupt-mode -m ...- 照常
nvmf_create_transport;TCP/RDMA 无额外 interrupt 专用传输参数;vfio-user 需-M(--disable-mappable-bar0)。 - 用
framework_get_reactors或spdk_top确认停流量后 reactor 进入 idle。
运维注意(官方):
- 所有用到的组件都要支持 interrupt mode,否则同线程上非 interrupt poller 会迫使继续狂轮询。
- TCP acceptor 在 interrupt mode 下忽略
acceptor_poll_rate(改事件驱动)。 - 预期 唤醒延迟略高于纯轮询;需调 NIC 中断聚合(moderation)在延迟与功耗间取舍。
- 限制:FC 不支持;TCP 的 uring socket 实现不支持 interrupt mode(需 POSIX socket 实现);非 Linux 不支持。
RDMA 集成要点(开发者节):在 verbs async fd、RDMA CM
event channel、以及 poller 的 completion channel
上注册中断,并用 ibv_req_notify_cq()
臂控完成通知——与
changelog「低负载减轮询、高吞吐仍可走轮询模型」一致。
4.3 何时开、何时不开
| 场景 | 建议 |
|---|---|
| 持续打满的性能向 target | 保持默认轮询;先保证核、NUMA、队列深度 |
| 昼夜负载差大、空闲核电/租核成本敏感 | 评估 --interrupt-mode;用官方方法确认
idle |
| 同线程混了不支持 interrupt 的 poller | 先拆线程/裁模块,否则开关形同虚设 |
| 延迟 SLO 极紧且负载始终高 | 中断模式的唤醒代价可能不合算;以实测为准(本篇不给数字) |
Initiator 侧 RDMA
interrupt(spdk_nvme_qpair_get_fd() 等)在
changelog 其他条目与 NVMe Driver / Interrupt Mode
文档中另有叙述;第 10 篇从 host 路径引用,避免与 target
--interrupt-mode 混成一个开关。
4.4 和「看起来像中断了但核还在转」相关的排障
官方 Troubleshooting 写得很直:若 idle 时线程仍
busy,先查 同线程是否还有非 interrupt
poller,或传输根本未支持 interrupt mode。RDMA
要确认 completion channel 创建与
ibv_req_notify_cq() 成功,否则 poll group
创建会失败;TCP 要确认 socket group 与 interrupt
注册成功。vfio-user 另需 guest 中断与
disable_mappable_bar0 配置一致。
换言之,--interrupt-mode
不是「省电魔法开关」,而是 全局契约:参与该
reactor 的每个
poller/传输都得遵守事件模型。混进一个强制轮询的组件,账本立刻回到空转税。这也是为什么第
2 篇强调「线程上挂了什么 poller」比「进程 CPU
百分比」更接近根因。
传输参数(I/O unit、in-capsule data size、max qpairs)与中断模式正交:前者塑造胶囊与内存布局,后者塑造空闲行为。调参时一次只动一类,否则无法归因。
从容量规划角度,轮询模式的成本函数接近「核数 × 占用率」,与瞬时 IOPS 弱相关;interrupt 模式则把成本推向「唤醒频率 × 唤醒代价」。白天打满、夜间接近空闲的网关,最适合认真评估 interrupt mode;七乘二十四打满的集中式阵列前端,则应先保证轮询路径的核与 NUMA,再谈省电。两种模式可以在不同环境复制同一套 subsystem 配置,但 进程启动参数与验证方法必须写进环境差异表,避免「测试机开了 interrupt、生产忘了」造成行为漂移。
同一 target 进程也可以同时创建 RDMA 与 TCP 两种 transport,再按 listener 分流不同客户端。这能降低迁移成本,但让排障维度加倍:必须先问「这条连接走的是哪一种 trtype」,再谈中断或轮询。混合传输时,interrupt mode 仍要求两侧组件都支持事件模型,否则一个传输拖住整核狂转。
五、开放问题
- 轮询与 interrupt 自动切换:changelog 强调保留两种模型;是否应在负载交叉点自动迁移,尚无被广泛采纳的策略——切错会在尾延迟与 CPU 之间震荡。
- TCP uring 路径与 interrupt mode 互斥:高性能套接字实现与省电事件模型暂时不能叠加,迫使部署二选一。
- 多租户共核:interrupt 唤醒后是否应做公平性调度,SPDK 数据面仍偏「专用核」假设;云场景下的共置策略未解。
- RDMA MR 上限与内存策略:预留大块 vs
运行时增长,在弹性容器场景如何与
--mem-size、巨页规格一起编排,仍缺可复制的标准作业程序。
补充争论:传输层要不要「永远零拷贝」
RDMA 路径的零拷贝是官方明确写出的性质;TCP 与未来传输未必能承诺同一句子。把「NVMe-oF = 零拷贝」当成口头禅,会在 TCP 部署里误导容量规划(CPU 与缓冲行为不同)。正确说法是:零拷贝是特定传输实现的性质,不是 NVMe-oF 规范对所有 fabric 的定理。
与第 2 篇 reactor 模型对照:传输层只是「在哪些 fd / CQ 上睡、在哪些路径上忙轮询」的具体化。选型文档若只写 RDMA/TCP 关键字而不写 空闲是否允许睡,运维会在电费单或云账单上才发现决策未完成。
六、参考资料
规范 / 官方文档(A)
- SPDK v26.01,Changelog(nvmf:RDMA interrupt-driven mode 原文)。
- SPDK v26.01,NVMe-oF Interrupt
Mode(
--interrupt-mode、TCP/RDMA/vfio-user、限制与验证)。 - SPDK v26.01,NVMe over Fabrics Target(RDMA/TCP 前提、MR 限制)。
- SPDK v26.01,NVMe-oF Target Programming Guide(RDMA CQ 共享、零拷贝、请求状态机)。
源码(A)
spdk/spdktag v26.01:lib/nvmf/rdma.c、lib/nvmf/tcp.c(官方 Interrupt Mode 开发者节指向的注册点)。
站内
实验台账
- 本篇无
spdk_top/ fio 输出;不给出 RDMA vs TCP 倍率。
传输层选完之后,再回头看第 8 篇的 listener 列表:每个地址背后都应能答出「RDMA 还是 TCP、轮询还是 interrupt」。
七、小结
- RDMA 与 TCP 是插件化传输:构建依赖、拷贝叙事、运维故障面不同。
- 默认轮询服务高吞吐;低负载 CPU 税是预期行为。
- v26.01 为 NVMe-oF RDMA
增加 interrupt-driven,并与 target 全局
--interrupt-mode(含 TCP 等)文档对齐;高吞吐仍可保留轮询。 - host 怎么连、与内核 initiator 如何分工——见 第 10 篇。
附:把「传输决策」写成可审查的三行
上线评审时,建议强制写下三行(可以很短,但不能空):
- Fabric:RDMA 或 TCP(及为何不是另一种)。
- 空闲模型:默认轮询,或
--interrupt-mode(及如何验证 idle)。 - 内存/注册约束:巨页规格与是否预留
--mem-size(RDMA 尤其关键)。
缺任何一行,后面的「性能调优」多半会变成在错误层拧旋钮。第 13–14 篇的观测与排障,也应以这三行为坐标原点。
→ 系列目录 · 上一篇:NVMe-oF target · 下一篇:Initiator 与互通
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【SPDK 用户态存储】用户态存储全景:从 Reactor 到 NVMe-oF 的坐标系
定位 SPDK 相对内核块层、O_DIRECT+io_uring 与 NVMe 协议百科的生态位;钉住 I/O 路径、线程模型、设备独占、块抽象、远端传输五条坐标系,并给出 16 篇阅读路线。
【SPDK 用户态存储】NVMe-oF Target:subsystem、listener 与 namespace→bdev
拆解 SPDK NVMe-oF target:lib/nvmf 中 tgt/subsystem/listener/namespace 与 bdev 的映射;说明 poll group 如何驱动 I/O,以及 app/nvmf_tgt 相对库的角色。
【SPDK 用户态存储】Initiator 与互通:SPDK host、内核 nvme-rdma/tcp 与多路径边界
对照 SPDK NVMe-oF host 路径与 Linux 内核 nvme-rdma/nvme-tcp initiator;厘清互通边界、bdev_nvme 附着方式,并在无伪造数字前提下说明多路径与 ANA 的谨慎用法。
【SPDK 用户态存储】排障坐标系:绑盘、空转、队列满、传输超时与多路径
把 SPDK 常见症状映射到绑盘/环境、reactor CPU、bdev 队列、传输超时、多路径五轴;给出可核对清单与观测入口,不虚构延迟数字与 RPC dump。