ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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 描述的是能力不是某一个具体协议。InfiniBandIB从链路层开始就是为 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 与 QPRDMA 应用不下“发送数据”这样的指令而是向工作队列下发一条工作请求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 上对端发来的数据在网卡上没有卸载目标只能丢弃或触发 RNRReceiver Not Ready错误。第二个是“为什么要轮询 CQ 而不是靠中断”。高吞吐下中断带来的上下文切换开销极高轮询 CQ 使延迟更稳定代价是被轮询的那个核始终占着。RDMA 说的 CPU 占用小是指数据面不耗 CPU但轮询本身仍然要一个线程空转这个区别在资源规划时经常被忽略。第三个是“零拷贝到底免掉了哪一次拷贝”。TCP 路径上至少有内核缓冲区和用户缓冲区之间的两次拷贝RDMA 只有用户缓冲区到网卡、网卡到对端用户缓冲区各一次 DMA数据不落在任何中间缓冲里。代价是注册内存区域时网卡要求锁定物理页并建立地址映射注册本身有成本所以高频发送的小消息通常需要预注册一块内存池循环复用而不是每条消息现注册现用。3.4 内存注册、Protection Domain 与权限用户空间传给网卡的地址是虚拟地址网卡做 DMA 时必须知道对应的物理地址因此需要注册内存区域MR。注册后返回两类 keylkey 供本地 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、ATOMICUC 不可靠连接不重传、保序只能与一个 QP 通信SEND、WRITEUD 不可靠数据报不重传、不保序可与任意 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); /* 创建 QPRC 模式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_WRITE或IBV_WR_RDMA_READ同时在wr.wr.rdma.remote_addr和wr.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 与 librdmacmrdma-core 的用户态由两个库分工。libibverbs 提供ibv_*接口负责 QP、CQ、MR 这些对象的生命周期librdmacm 在 verbs 之上封装连接管理提供rdma_create_qp、rdma_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 的钱花在哪就一目了然了。本文还有配套的精品资源点击获取
返回列表