news 2026/9/17 20:22:31

RDMA为什么快?协议选型、队列模型与编程实践全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RDMA为什么快?协议选型、队列模型与编程实践全解析

简介:这是一份面向网络工程师、高性能计算与分布式存储开发者的RDMA技术调研PDF文档。文档从传统网络协议栈的痛点切入,系统梳理了RDMA零拷贝、内核旁路、CPU卸载三大核心优势,并对比Infiniband、RoCE、iWARP三种协议的演进脉络与适用场景,帮助读者快速建立技术选型框架。此外,文档还详细解释了WQ、QP、CQ等关键术语,结合SEND/RECV、READ/WRITE等通信操作说明了一次RDMA传输的软硬件交互流程,并介绍了verbs与rdma-core等编程接口,适合作为入门学习与内部培训资料。资源为1个PDF文件,大小约1.01MB,内容精炼、图文结合,方便离线阅读与团队分享。目前已有527人学习,对于希望理解RDMA基本原理与落地路径的读者,这是一份值得收藏的调研笔记。

1. RDMA 为什么能让 CPU 退出数据通路

一条经常被忽略的事实:RDMA 的快不来自网卡速率本身,而来自数据路径变短。同样是 100GbE,传统 TCP 路径上的数据先由 DMA 进内核缓冲区,再由 CPU 参与协议栈封包、校验、中断,最后从内核拷回用户缓冲区;RDMA 网卡直接按应用发布的工作请求,从用户缓冲区 DMA 数据,经网络直接 DMA 到对端用户预注册的缓冲区,CPU 只剩控制面。远程直接内存访问这个名字准确概括了这件事:读写远程主机内存而不中断远程 CPU。低延迟、高带宽、低 CPU 占用是它的三个核心指标。需要确定性延迟的金融与 HPC,以及把存储放在以太网上的云计算和 AI 集群,都在用 RDMA。下面按协议选型、队列模型、verbs 编程和排错四条线展开这份调研。

2. 三种RDMA协议选型:IB、RoCE与iWARP的取舍

2.1 一个名字、三套协议栈

RDMA 描述的是能力,不是某一个具体协议。InfiniBand(IB)从链路层开始就是为 RDMA 设计的全新网络,网卡和交换机都要专用;RoCE 把 IB 的报文头套在以太网帧里,让 RDMA 可以跑在已有以太网交换机上;iWARP 则把 RDMA 语义映射到 TCP 之上。三者对上层都暴露同一套 verbs 接口,所以应用层创建 QP、下发 WR 的代码可以一致,差异几乎全部集中在链路层和运维上。

2.2 IB、RoCE、iWARP 的横向对比

对比维度InfiniBandRoCEiWARP
链路层专属 IB 链路以太网以太网
报文承载完整 IB 协议栈以太网/IP/UDP 上承载 IB 报文头TCP/IP 上承载 RDMA 语义
交换机必须 IB 专用交换机支持无损特性的一般交换机普通以太网交换机
丢包处理下层保证有序不丢需 PFC/DCQCN 提供无损保障TCP 重传兜底
CPU 开销最低中,依赖 TCP 卸载能力
典型场景HPC、GPU 集群数据中心存储、云计算跨 TCP 网络的老旧兼容场景

上表的关键不在选哪个更好,而在背后的约束不同。IB 的隔离性最好,但整个网络要单独建设;RoCE 复用现有运维体系,但要求交换机支持无损队列;iWARP 看似兼容最省事,但 TCP 层重传和窗口管理一旦交给 CPU 处理,就失去 RDMA 的大部分优势,现代数据中心已经很少把它作为主力方案。如果选 iWARP 且网卡不支持 TCP 卸载,驱动栈会在软件里模拟 RDMA 语义,延迟和吞吐都会明显劣化。

2.3 为什么 RoCE 需要一个“不像以太网”的以太网

RoCE v2 的报文结构是以太网头、IP、UDP,后面再挂 IB 的基础传输头。之所以走 UDP,是为了让现有三层交换机也能路由 RoCE 报文而不需要操作系统参与,按照常见约定使用 UDP 4791 端口。麻烦也出在这:IB 原本假设链路有序不丢,而普通以太网没有这个保证,UDP 也没有重传能力,RoCE 接收端遇到乱序或丢帧就直接丢弃数据。

所以 RoCE 实际部署时要把以太网改造成“看起来像 IB”的无损网络,典型手段是交换机开启 PFC(优先级流控),再配合 ECN 和显式拥塞通知做闭环,比如 DCQCN 拥塞控制方案。PFC 会在某一优先级队列缓存耗尽时暂停上游端口,代价是同一队列上的 TCP 流也被堵住;因此启用 PFC 的队列最好只承载 RoCE 业务。这是 RoCE 选型时最容易低估的工程量,普通千兆交换机如果没有无损功能,跑起来性能会很差。

2.4 生产环境的选型判断

预算充足且准备从头建一张高性能网络时,IB 最省心,GPU 直连和并行文件系统场景里生态也最成熟。在已有万兆或百G 以太网机房里补一个存储池,RoCE 改造网络的成本远低于换交换机。iWARP 几乎只出现在需要穿越传统 TCP 网络且不能改链路的个别情况。网卡层面,Mellanox ConnectX 系列是 RDMA 生态里最常见的硬件选择,采购时注意确认网卡固件里开启了 RDMA 功能,以及驱动版本与内核匹配。

3. QP、CQ与内存注册:RDMA通信的基本链路

3.1 工作队列模型:SQ、RQ 与 QP

RDMA 应用不下“发送数据”这样的指令,而是向工作队列下发一条工作请求(WR)。发送队列 SQ 里放的是“把这段内存发给对端”这类请求,接收队列 RQ 里放的是“我准备好了一块缓冲区”。SQ 和 RQ 合起来称为队列对 QP,通信双方每建立一条逻辑连接就需要一个 QP。任务完成后,硬件往完成队列 CQ 里写一个完成事件,应用轮询 CQ 就知道这次 WR 结束了。QP、CQ 都是网卡硬件对象,用户态只是通过 verbs 库管理它们。

3.2 一次 SEND-RECV 的事件顺序

把队列放一起看一次典型的 SEND-RECV 交互,其顺序不是直觉里的“先发再收”,而是接收方先准备好:

参与者动作说明
接收端应用向 RQ 发布 RECV把用户态缓冲区提前交给网卡
发送端应用向 SQ 发布 SEND指定发送缓冲区地址和长度
发送端网卡DMA 数据并组装报文不经过内核协议栈,直接上链路
接收端网卡DMA 写入用户缓冲区并回 ACK接收端 CPU 完全不碰数据
双方 CQ分别产生完成事件发送端在收到 ACK 后得到完成通知

发送端应用在收到完成通知之前不能改动缓冲区数据;接收端收到完成事件才知道缓冲区里已经有数据,但还要靠自己的应用逻辑去判断数据完整性。整个数据面没有任何一次上下文切换,这就是“内核旁路”的落地形态。也正因如此,QP 的状态管理工作比 socket 复杂得多:连接建好之后,双方还要维护发送窗口、包序号和重传状态。

3.3 三个容易卡住理解的设计

第一个是“接收端为什么要提前发布 RECV”。传统 socket 有内核接收缓冲区兜底,数据先进内核再通知应用;RDMA 没有内核缓冲区这层,应用如果不提前把内存区域注册并挂到 RQ 上,对端发来的数据在网卡上没有卸载目标,只能丢弃或触发 RNR(Receiver Not Ready)错误。

第二个是“为什么要轮询 CQ 而不是靠中断”。高吞吐下中断带来的上下文切换开销极高,轮询 CQ 使延迟更稳定,代价是被轮询的那个核始终占着。RDMA 说的 CPU 占用小,是指数据面不耗 CPU,但轮询本身仍然要一个线程空转,这个区别在资源规划时经常被忽略。

第三个是“零拷贝到底免掉了哪一次拷贝”。TCP 路径上至少有内核缓冲区和用户缓冲区之间的两次拷贝,RDMA 只有用户缓冲区到网卡、网卡到对端用户缓冲区各一次 DMA,数据不落在任何中间缓冲里。代价是注册内存区域时网卡要求锁定物理页并建立地址映射,注册本身有成本,所以高频发送的小消息通常需要预注册一块内存池循环复用,而不是每条消息现注册现用。

3.4 内存注册、Protection Domain 与权限

用户空间传给网卡的地址是虚拟地址,网卡做 DMA 时必须知道对应的物理地址,因此需要注册内存区域(MR)。注册后返回两类 key:lkey 供本地 SGE 使用,rkey 连同远程虚拟地址一起告诉对端,对端才能发起 READ 或 WRITE。RDMA 不加锁、不通知对端进程,rkey 一旦泄漏,任何知道该地址的远端 QP 都有权写这块内存,这就是 RDMA 的权限边界所在。

多个资源要放进一个保护域(PD)里管理。QP 只能访问同一 PD 下的 MR,两个互不相关的应用用不同 PD 就可以隔离资源;Mellanox 网卡也支持在 MR 之上再开内存窗口进行更细粒度的权限控制,适合把大块注册内存划成多个逻辑窗口分别授权。写生产代码时,一个简单原则是:注册时只给当前业务真正需要的访问权限,不要图省事把所有权限一次打开。

4. SEND/RECV与WRITE/READ:通信操作、传输模式与verbs编程

4.1 四种通信操作的语义差异

SEND/RECV 成对出现,接收端指定接收缓冲区,数据落在哪完全由接收端控制,语义接近“把一条消息传给对方”。RDMA WRITE 则是由发送端直接写对端内存的某个 remote_addr,数据到达时接收端无感知;RDMA READ 是发送端从对端内存读数据回来,过程同样对远端进程透明。第四种 ATOMIC 只支持少量原子操作,比如原子加和比较交换,用于分布式锁和计数器。

在实际工程里,大规模数据多用 WRITE/READ 传输,因为省去了接收端 post 缓冲区的开销,也不需要接收端进程在数据到达时做事件处理;但“对端无感知”也带来同步问题。常见做法是用 RDMA WRITE 传完数据后,再补一个带即时数的 SEND 或一个原子操作作为“门铃”,告诉对端数据已就位,接收端再轮询完成队列去消费。这种方式比每次数据都走 SEND/RECV 吞吐更高,但代码复杂度也上一个台阶。

4.2 RC、UC、UD 与 DCT 的取舍

传输模式的选择直接决定支持哪些操作,这张表值得收藏:

传输模式可靠性单 QP 可通信对端支持的操作
RC 可靠连接可靠、保序、重传只能与一个 QP 通信SEND、WRITE、READ、ATOMIC
UC 不可靠连接不重传、保序只能与一个 QP 通信SEND、WRITE
UD 不可靠数据报不重传、不保序可与任意 QP 通信仅 SEND,负载受 MTU 限制
DCT 动态连接可靠语义一个 QP 与多个远端 QP 通信近似 RC,降低连接数

RC 最容易写对,但每对连接都要建独立 QP,大规模场景下内存和上下文开销很高。UD 不建连接,任意 QP 之间都能互发小消息,适合控制信令,但单包长度受 MTU 限制,也没有动词层的重传。DCT(动态连接传输)把 RC 的可靠性扩展成动态连接,解决的是大量短连接场景下 QP 太多的问题,代价是支持面不如 RC 老,遇到老版本固件或驱动要先确认兼容性。

4.3 一个最小可用的 verbs 调用链

用 rdma-core 写程序,最核心的链路是打开设备、分配 PD、注册 MR、创建 CQ、创建 QP、下发 WR、轮询 CQ。下面的骨架省略了双端握手细节,只展示调用顺序:

#include <infiniband/verbs.h> /* 打开设备:RDMA 设备通常叫 mlx5_0/mlx5_1,由驱动注册 */ struct ibv_device **devs = ibv_get_device_list(NULL); struct ibv_context *ctx = ibv_open_device(devs[0]); /* PD:绑定本端资源,也决定同名 MR/QP 的隔离边界 */ struct ibv_pd *pd = ibv_alloc_pd(ctx); /* 注册内存:对网卡可见,REMOTE_WRITE 表示允许远端写这块区域 */ struct ibv_mr *mr = ibv_reg_mr(pd, buf, buf_size, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE); /* 完成队列:深度表示 CQ 最多挂多少个未消费的完成事件 */ struct ibv_cq *cq = ibv_create_cq(ctx, 64, NULL, NULL, 0); /* 创建 QP:RC 模式,SQ/RQ 深度各 16,每个 WR 最多 1 个 SGE */ struct ibv_qp_init_attr attr = { .qp_type = IBV_QPT_RC, .send_cq = cq, .recv_cq = cq, .cap.max_send_wr = 16, .cap.max_recv_wr = 16, .cap.max_send_sge = 1, .cap.max_recv_sge = 1, }; struct ibv_qp *qp = ibv_create_qp(pd, &attr); /* 之后还有三次状态迁移:RESET -> INIT -> RTR -> RTS, RTR 需要交换对端 QPN、GID、PSN 等地址信息, RTS 阶段配置超时与重试参数 */ /* 构造并发送一个 SEND WR */ struct ibv_sge sge = { .addr = (uint64_t)buf, .length = buf_size, .lkey = mr->lkey, /* 本地 key 从 MR 拿到 */ }; struct ibv_send_wr wr = { .opcode = IBV_WR_SEND, .send_flags = IBV_SEND_SIGNALED, /* 必须请求生成完成事件 */ .sg_list = &sge, .num_sge = 1, }; struct ibv_send_wr *bad_wr = NULL; ibv_post_send(qp, &wr, &bad_wr); /* 轮询 CQ 等待完成 */ struct ibv_wc wc; while (ibv_poll_cq(cq, 1, &wc) == 0); if (wc.status != IBV_WC_SUCCESS) { /* 从 wc.status 转成字符串即可定位错误类型 */ }

这里几个参数的坑要注意。一是IBV_SEND_SIGNALED不能省,没有它硬件不会针对这个 WR 生成完成事件,轮询线程会一直空转;二是如果改成 WRITE/READ,将 opcode 换成IBV_WR_RDMA_WRITEIBV_WR_RDMA_READ,同时在wr.wr.rdma.remote_addrwr.wr.rdma.rkey中填入对端地址和 rkey;三是max_send_wr必须大于发送端同时挂起(在途)的 WR 数,否则 post_send 会返回非零错误码。opcode 每次只能指定一个,多语义的复杂请求要拆成多个 WR。

4.4 RNR 重试、队列深度与超时参数

RNR 错误发生在发送端抢先于接收端发布 RECV 时。接收端网卡收到没有接收缓冲区的 SEND 请求,会回一个 RNR NAK;发送端依据rnr_retry参数重试。设置偏小,一次延迟就会导致任务失败;设置偏大,接收端一直不 post 时延迟被无意义放大。生产上应该在接收端把max_recv_wr调大,甚至用 SRQ(共享接收队列)让多个连接共享一个接收缓冲池,而不是指望重试来兜住设计缺口。

提示:CQ 里出现IBV_WC_RNR_RETRY_EXC_ERR时,先查接收端是不是没有及时 post_recv,再查队列深度,最后才调 rnr_retry 参数。

链路层参数同样影响行为:timeout控制每次重传的时间间隔,retry_count控制请求重传次数;RoCE 环境还要确认 GID 索引和 VLAN 优先级与交换机配置一致,否则会出现偶发性连接断开或建链失败。

5. rdma-core库分工、环境验证与高频排错

5.1 libibverbs 与 librdmacm

rdma-core 的用户态由两个库分工。libibverbs 提供ibv_*接口,负责 QP、CQ、MR 这些对象的生命周期;librdmacm 在 verbs 之上封装连接管理,提供rdma_create_qprdma_connect这类接口,省去手工交换 QPN、GID 和状态迁移的过程。写连接密集型程序时,用 rdmacm 比裸 verbs 快得多,也更容易控制超时和重试。

5.2 快速验证 RDMA 环境是否可用

# 列出本机有哪些 RDMA 设备 ibv_devices # 查看设备能力,确认端口 UP,并拿到 GID 索引 ibv_devinfo -d mlx5_0 -v | grep -A 6 "port 1" # 服务端先起带宽测试,监听默认端口 ib_write_bw -d mlx5_0 --report_gbits # 客户端指定对端 IP,-s 控制消息大小 ib_write_bw -d mlx5_0 -s 65536 --report_gbits 192.168.1.2

ibv_devinfo确认端口速度和实际链路一致,再看单播 GID 列表里有没有 IPv4 或 IPv6 地址。RoCE 环境一般需要用-x指定 GID 索引,常见取值为 3,但以设备实际输出为准;GID 配错会导致建链失败但不是硬件故障。

5.3 三个实际排错现场

第一个是“RNR retry exceeded”。除了把接收端队列调深之外,还要检查是否在接收路径上给每个连接预留了足够的接收 WR,有些库在初始化时按最小值分配,低负载看不出问题,高并发一上来就报 RNR。第二个是“容器里找不到 mlx5_0”。RDMA 设备不是普通网络设备,容器要看到它必须走 SR-IOV 的 VF 透传或 vfio-pci 直通,单靠网络命名空间和端口映射无法让 verbs 层枚举到设备。第三个是“RoCE 吞吐跑不满”。先执行ethtool -S看端口计数,如果 rx_discards 或 buffer overrun 持续增长,多半是交换机侧丢帧;RoCE 没有 TCP 那样的可靠重传,千分之一的丢包率就会让吞吐明显下跌,需要把对应队列的 PFC 打开,并确保链路两端和交换机处于同一套无损配置中。

5.4 RDMA 的权限边界

RDMA WRITE/READ 一旦生效,远端进程并不感知,数据面没有协议栈去拦截它。因此注册 MR 时只给必要权限,不让不可信节点共享 rkey,跨节点通信只暴露需要访问的内存区域,并用 PD 隔离不同应用。RoCE v2 报文通常走 UDP 4791,普通防火墙规则拦不住硬件直接 DMA 的转发路径,物理接入控制反而更实际。把队列深度调大、GID 对齐、PFC 打开,再分别跑一次 TCP 和 RDMA 的带宽与 CPU 占用对比,RDMA 的钱花在哪就一目了然了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 20:19:26

Rivet RunnersApi 完全指南:使用 Rust SDK 列出与管理 Runner 状态

Rivet RunnersApi 完全指南&#xff1a;使用 Rust SDK 列出与管理 Runner 状态 【免费下载链接】actors Rivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution. 项目地址: https://gitcode.com/GitHub_T…

作者头像 李华
网站建设 2026/9/17 20:17:24

管理后台功能需求文档模板v1.1:docx结构化编写与权限管理实践

简介&#xff1a;这是针对个人消费贷款申请管理后台的功能需求文档模板&#xff0c;面向产品经理、运营人员和开发技术人员&#xff0c;用于统一各方对后台功能与审批流程的认知&#xff0c;减少开发与沟通返工。模板以贷款申请到放款为主线&#xff0c;定义了贷款用户、业务员…

作者头像 李华
网站建设 2026/9/17 20:14:27

04. 如何自定义快捷菜单栏和工具栏? I ANSA 设计小诀窍系列

大家好。在使用ANSA进行网格划分时&#xff0c;高频操作往往分散在不同菜单下&#xff0c;频繁切换会显著拖慢工作效率。如果能将常用功能集中到自定义的选项卡和工具栏中&#xff0c;就可以大幅减少点击次数&#xff0c;让操作更顺手。本次分享将带你掌握ANSA中自定义快捷菜单…

作者头像 李华
网站建设 2026/9/17 20:14:13

Python爬取猫眼电影Top100:数据分析与可视化实战

简介&#xff1a;面向计算机专业学生与Python实战学习者的猫眼电影数据综合项目&#xff0c;覆盖网络爬虫、数据清洗、数据库存储、Web可视化等完整流程&#xff0c;适合作为毕业设计、课程设计或期末大作业。压缩包共86个文件&#xff0c;体积仅1.05MB&#xff0c;内部包含9个…

作者头像 李华