ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RDMA技术调研:InfiniBand、RoCE与iWARP选型及verbs编程实战

RDMA技术调研:InfiniBand、RoCE与iWARP选型及verbs编程实战 简介这份PDF文档面向希望系统了解RDMA远程直接内存访问技术的开发者、运维人员与高性能网络学习者围绕其原理、协议与编程方法展开调研梳理。内容从DMA与RDMA的基本概念切入对比传统网络协议栈的转发过程讲解零拷贝、内核旁路、CPU卸载三大核心优势并说明低延迟、高带宽、低CPU占用等典型业务场景。文档进一步介绍Infiniband、RoCE、iWARP三种RDMA协议差异以及WQ、SQ、RQ、CQ、QP等关键术语与SEND/RECV、WRITE/READ、ATOMIC等通信操作还涉及RC、UC、UD、DC等传输模式与rdma-core用户态库。资源包为1个PDF文件大小约1.01MB结构紧凑、便于通读。目前已有528人学习适合作为入门到进阶阶段的技术调研参考帮助读者建立RDMA知识框架并理解其编程接口与部署要点。1. RDMA 技术调研从一份 PDF 标题出发把远程直接内存访问拆到能落地看到「RDMA技术调研 .pdf」这个标题我第一反应不是去找那份 PDF而是想搞清楚一件事为什么现在做分布式存储、AI 训练集群、高性能数据库的人都绕不开 RDMA 这个词。RDMA 全称 Remote Direct Memory Access远程直接内存访问核心就一句话——让一台机器直接读写另一台机器的内存不经过对方 CPU不经过内核协议栈。传统 TCP 收发数据数据要从用户态拷到内核态再从内核态拷到网卡接收端反过来再拷两遍CPU 全程陪着。RDMA 把这两次拷贝和内核参与全省掉网卡直接 DMA 到应用缓冲区。适合谁做分布式系统、存储后端、GPU 集群通信、高频交易中间件的工程师。这份调研要解决的不是「RDMA 是什么」而是「我该选哪个方案、怎么跑通、参数怎么调、哪里会翻车」。2. RDMA 三种实现路线InfiniBand、RoCE 与 iWARP 怎么选2.1 三种路线到底差在哪RDMA 不是单一协议它是一组能力的统称底层可以跑在三种网络上。InfiniBand 是专用网络从网卡到交换机整套都是为 RDMA 设计的性能最好、延迟最低但交换机和线缆成本高生态相对封闭。RoCE 是 RDMA over Converged Ethernet把 RDMA 跑在以太网上分 v1 和 v2 两个版本v2 支持路由是现在数据中心的主流选择。iWARP 是 RDMA over TCP能跑在普通 TCP/IP 网络上兼容性最好但性能和生态都不如 RoCE。选型逻辑其实不复杂。预算充足、追求极致延迟、机房可以整套换设备选 InfiniBand。已有以太网基础设施、想平滑升级、团队有网络调优能力选 RoCE v2。跨广域网、网络环境不可控、只想要 RDMA 的零拷贝语义而不追求极限延迟考虑 iWARP。我见过最多的翻车场景是团队买了 RoCE 网卡插到普通交换机上以为插上就能用结果性能还不如 TCP。RoCE v2 对网络的要求是「无损」需要 PFC 和 ECN 配合普通交换机不支持这些流控机制丢包一发生RDMA 的重传逻辑比 TCP 还慢。维度InfiniBandRoCE v2iWARP底层网络专用 IB以太网TCP/IP典型延迟亚微秒1-3 微秒5-10 微秒交换机要求IB 交换机支持 PFC/ECN普通交换机运维复杂度中高低适用场景HPC、AI 集群数据中心跨网段2.2 确认硬件和驱动是否就绪不管选哪条路线第一步都是确认硬件识别正常。以 Mellanox现在叫 NVIDIA Networking网卡为例这是 RoCE 和 InfiniBand 最常见的硬件。先看网卡是否被系统识别再看驱动版本和固件版本是否匹配。# 查看 RDMA 设备列表正常应该看到 mlx5_0 之类的设备名 ibv_devices # 查看设备详细信息包括固件版本、端口状态、链路速率 ibv_devinfo -d mlx5_0 # 查看网卡 PCI 信息确认驱动加载 lspci | grep -i mellanox # 查看内核模块是否加载 lsmod | grep mlx5ibv_devices列出所有可用的 RDMA 设备如果为空说明驱动没加载或者硬件没识别。ibv_devinfo里的state字段很关键PORT_ACTIVE才是正常PORT_DOWN说明链路没起来。fw_ver是固件版本RoCE v2 对固件有最低要求太老的固件不支持 ECN 标记。lsmod看mlx5_ib和mlx5_core两个模块缺一个都不行。如果ibv_devices有输出但ibv_devinfo报错大概率是用户态库版本和内核模块不匹配用ofed_info -s看 OFED 栈版本。2.3 RoCE v2 的无损网络配置RoCE v2 的性能命脉在无损网络。所谓无损就是保证不丢包。RDMA 的传输层假设网络不丢包一旦丢包它的重传机制比 TCP 粗暴得多性能断崖式下跌。实现无损靠两个机制PFCPriority Flow Control和 ECNExplicit Congestion Notification。PFC 在链路层做流控快撑爆的时候发暂停帧。ECN 在 IP 层做标记让发送端主动降速。# 在交换机侧配置 PFC通常对 priority 3 开启 # 以下为 Linux 主机侧查看 PFC 统计的命令 mlnx_qos -i eth0 --show-pfc # 查看 ECN 统计 mlnx_qos -i eth0 --show-ecn # 设置 DSCP 到 priority 的映射RoCE v2 通常用 DSCP 26 映射到 priority 3 mlnx_qos -i eth0 --dscp2prio set,26,3 # 确认网卡信任模式为 DSCP mlnx_qos -i eth0 --trust dscp--show-pfc看每个 priority 的暂停帧收发计数如果rx_pause和tx_pause持续增长说明网络在频繁触发流控虽然不丢包但性能已经受影响。--dscp2prio把 RoCE 流量标记到特定 priority交换机侧要对同一个 priority 开 PFC。--trust dscp让网卡按 DSCP 字段而不是 802.1p 来分类流量。这里最常见的坑是主机侧配了、交换机侧没配或者两边 priority 对不上流量进了默认队列PFC 根本没生效。3. 用 perftest 跑通第一组 RDMA 性能数据3.1 perftest 工具集的安装与选择perftest 是 RDMA 性能测试的事实标准工具集包含ib_send_bw、ib_send_lat、ib_read_bw、ib_write_bw等十几个命令。安装方式取决于发行版Ubuntu 直接apt install perftestRHEL 系用yum install perftest也可以从 OFED 栈里带出来。装完先确认版本不同版本的参数名有差异。# 安装 perftest apt update apt install -y perftest # 确认版本 ib_send_bw --version # 查看所有可用测试工具 ls /usr/bin/ib_*ib_send_bw测的是双边操作send/receive需要接收端也参与。ib_write_bw和ib_read_bw测的是单边操作RDMA Write/Read接收端 CPU 完全不参与这才是 RDMA 的真正威力所在。做调研时我建议三个都跑ib_send_bw看基础通信能力ib_write_bw看单边写带宽ib_read_bw看单边读带宽。延迟测试用ib_send_lat和ib_write_lat。3.2 服务端与客户端的最小测试命令测试需要两台机器一台做 server 一台做 client。server 端先起监听client 端再发起连接。以下命令假设两台机器都有mlx5_0设备且网络已配通。# 服务端执行 # 监听在 mlx5_0 设备上端口 18515等待客户端连接 ib_write_bw -d mlx5_0 -i 1 -p 18515 --report_gbits # 客户端执行 # 连接到服务端 IP跑 10 秒输出带宽 ib_write_bw -d mlx5_0 -i 1 -p 18515 --report_gbits 192.168.1.100-d mlx5_0指定 RDMA 设备多网卡机器必须指定否则可能选错。-i 1指定端口号IB 网卡通常用 1RoCE 双口网卡要看实际接线。-p 18515是 TCP 端口只用于交换 RDMA 连接信息实际数据走 RDMA。--report_gbits让输出以 Gb/s 为单位不加的话默认是 MB/s容易看混。客户端最后的 IP 是服务端的管理网 IP不是 RDMA 网 IP这个连接只用来交换 QP 信息。3.3 关键参数怎么调才有参考价值默认参数跑出来的数据参考价值有限因为消息大小、队列深度、测试时长都会显著影响结果。做调研时至少要覆盖几个维度消息大小从 2 字节到 8MB 扫一遍队列深度-q从 1 到 16 扫一遍测试时长-D至少 10 秒避免抖动。# 扫描消息大小从 2 字节到 8MB队列深度 4跑 10 秒 ib_write_bw -d mlx5_0 -i 1 -p 18515 --report_gbits \ -q 4 -D 10 -s 2 --run_infinitely 2/dev/null || \ for size in 2 64 256 1024 4096 65536 1048576 8388608; do ib_write_bw -d mlx5_0 -i 1 -p 18515 --report_gbits \ -q 4 -D 10 -s $size 192.168.1.100 done-q 4是队列深度也就是同时有多少个未完成的 WRWork Request。队列深度太小带宽上不去因为流水线填不满。队列深度太大延迟会涨因为排队时间变长。-D 10跑 10 秒比默认的 5 秒更稳。-s指定消息大小小消息看延迟大消息看带宽。--run_infinitely是某些版本的参数不一定都有所以上面用循环兜底。看结果时注意两个数BW peak是峰值带宽BW average是平均带宽两者差距大说明有抖动。4. RDMA 编程入门从 verbs 接口到第一个可运行程序4.1 verbs 编程模型的核心概念RDMA 编程用的是 verbs API这是一套用户态接口核心对象有四个PDProtection Domain、MRMemory Region、CQCompletion Queue、QPQueue Pair。PD 是权限边界MR 是注册过的内存CQ 是完成事件队列QP 是收发队列对。QP 又分 SQSend Queue和 RQReceive QueueSQ 放发送请求RQ 放接收请求。整个流程是打开设备 → 分配 PD → 注册 MR → 创建 CQ → 创建 QP → 交换 QP 信息 → 修改 QP 状态到 RTS → 收发数据 → 轮询 CQ。其中「交换 QP 信息」是必须走带外通道的通常用 TCP socket 交换 LID、GID、QPN 这些参数。QP 状态迁移是新手最容易卡住的地方必须按 RESET → INIT → RTR → RTS 的顺序走跳步或者参数填错都会导致ibv_modify_qp返回错误。4.2 一个最小可运行的 RDMA Write 示例下面这段代码展示 RDMA Write 的核心流程省略了错误处理和 TCP 交换细节重点看 verbs 调用顺序。#include infiniband/verbs.h #include stdio.h #include stdlib.h #include string.h #define BUF_SIZE 4096 int main() { // 1. 获取设备列表 int num_devices; struct ibv_device **dev_list ibv_get_device_list(num_devices); if (!dev_list || num_devices 0) { fprintf(stderr, no RDMA device found\n); return 1; } // 2. 打开第一个设备 struct ibv_context *ctx ibv_open_device(dev_list[0]); if (!ctx) { fprintf(stderr, open device failed\n); return 1; } // 3. 查询设备能力端口数、最大 QP 等 struct ibv_device_attr dev_attr; ibv_query_device(ctx, dev_attr); printf(device: %s, max_qp: %d\n, ibv_get_device_name(dev_list[0]), dev_attr.max_qp); // 4. 分配 PD struct ibv_pd *pd ibv_alloc_pd(ctx); if (!pd) { fprintf(stderr, alloc pd failed\n); return 1; } // 5. 分配并注册 MR必须用 posix_memalign 对齐 char *buf; if (posix_memalign((void **)buf, 4096, BUF_SIZE)) { fprintf(stderr, memalign failed\n); return 1; } memset(buf, 0, BUF_SIZE); struct ibv_mr *mr ibv_reg_mr(pd, buf, BUF_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ); if (!mr) { fprintf(stderr, reg mr failed\n); return 1; } printf(mr lkey: 0x%x, rkey: 0x%x\n, mr-lkey, mr-rkey); // 6. 创建 CQ深度 16 struct ibv_cq *cq ibv_create_cq(ctx, 16, NULL, NULL, 0); if (!cq) { fprintf(stderr, create cq failed\n); return 1; } // 7. 创建 QP发送和接收 CQ 都用同一个 struct ibv_qp_init_attr qp_attr { .send_cq cq, .recv_cq cq, .qp_type IBV_QPT_RC, // Reliable Connection .cap { .max_send_wr 16, .max_recv_wr 16, .max_send_sge 1, .max_recv_sge 1, }, }; struct ibv_qp *qp ibv_create_qp(pd, qp_attr); if (!qp) { fprintf(stderr, create qp failed\n); return 1; } printf(qp num: %d\n, qp-qp_num); // 8. 修改 QP 状态到 INIT struct ibv_qp_attr attr { .qp_state IBV_QPS_INIT, .pkey_index 0, .port_num 1, .qp_access_flags IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ, }; if (ibv_modify_qp(qp, attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS)) { fprintf(stderr, modify qp to INIT failed\n); return 1; } // 9. 此处应通过 TCP 交换 QPN、LID、GID、rkey、addr // 然后修改 QP 到 RTR再修改到 RTS // 实际代码中这部分需要根据对端信息填充 attr printf(RDMA resources ready, qp state: INIT\n); // 10. 清理 ibv_destroy_qp(qp); ibv_destroy_cq(cq); ibv_dereg_mr(mr); free(buf); ibv_dealloc_pd(pd); ibv_close_device(ctx); ibv_free_device_list(dev_list); return 0; }编译命令是gcc -o rdma_demo rdma_demo.c -libverbs。这段代码跑到 INIT 状态就停了因为 RTR 和 RTS 需要知道对端的 QPN、LID、GID 和 rkey这些必须通过带外通道交换。ibv_reg_mr的访问标志很关键IBV_ACCESS_LOCAL_WRITE允许本地写IBV_ACCESS_REMOTE_WRITE允许对端写你的内存IBV_ACCESS_REMOTE_READ允许对端读你的内存。做 RDMA Write 测试时接收端必须开REMOTE_WRITE否则对端写不进来。posix_memalign的对齐要求是页对齐通常 4096不对齐会导致注册失败或者性能下降。4.3 QP 状态迁移的四个阶段QP 从创建到能用必须经过四次状态迁移每次迁移要填不同的字段。这是 verbs 编程里最繁琐也最容易出错的部分。状态含义必须填的字段RESET刚创建无INIT初始化pkey_index, port_num, qp_access_flagsRTR准备接收dest_qp_num, rq_psn, path_mtu, ah_attrRTS准备发送sq_psn, timeout, retry_cnt, rnr_retrydest_qp_num是对端的 QPN通过 TCP 交换得到。rq_psn和sq_psn是包序列号两端要匹配。path_mtu是路径 MTURoCE 通常用 1024 或 4096InfiniBand 用 4096。ah_attr是地址句柄InfiniBand 填 LIDRoCE 填 GID。timeout和retry_cnt控制重传行为设太小容易误重传设太大故障恢复慢。我一般先用保守值timeout 14约 4 秒、retry_cnt 7、rnr_retry 7跑通再调优。5. RDMA 落地避坑五条血泪经验5.1 现象ibv_devices 有输出但 ib_write_bw 报错原因通常是用户态库和内核模块版本不匹配。OFED 栈升级后libibverbs和mlx5_ib模块版本可能不一致导致 verbs 调用返回ENOSYS或EINVAL。解决方法是确认ofed_info -s和modinfo mlx5_ib | grep version输出一致不一致就重装 OFED 或者重启加载新模块。另一个可能是ib_uverbs模块没加载modprobe ib_uverbs补上。5.2 现象RoCE 带宽只有 TCP 的一半先查 PFC 和 ECN 是否生效。mlnx_qos -i eth0 --show-pfc看暂停帧计数如果为 0 说明 PFC 没触发可能交换机侧没配。再查 MTURoCE 对 MTU 敏感ip link show eth0看 MTU 是不是 9000 或至少 4200默认 1500 会严重限制带宽。最后查 DSCP 映射mlnx_qos -i eth0 --show-dscp确认 RoCE 流量进了正确的 priority 队列。这三个都对了带宽还上不去看是不是跑在了 v1 模式cma_roce_mode -d mlx5_0 -p 1确认是 v2。5.3 现象ib_write_bw 跑小消息延迟正常大消息带宽上不去队列深度不够。默认-q 1只能填一个 WR大消息传输时间长流水线空转。把-q加到 4 或 8 再测。另一个原因是 MR 没对齐posix_memalign的对齐值要等于系统页大小getpagesize()确认。还有可能是 PCIe 带宽瓶颈lspci -vv -s bdf看链路宽度和速率x8 Gen3 理论 8GB/s跑 100Gbps 网卡刚好够跑 200Gbps 就不够了。5.4 现象QP 修改到 RTR 返回 EINVAL最常见的原因是path_mtu设得比实际链路 MTU 大。RoCE 链路 MTU 是 1500path_mtu设 4096 就会失败。用ibv_devinfo看active_mtu字段按实际值设。另一个原因是ah_attr里的 GID 索引不对RoCE v2 要用 RoCE v2 的 GIDshow_gids命令看 GID 表选对索引。InfiniBand 则是 LID 填错ibstat看端口 LID。5.5 现象程序跑一段时间后 CQ 轮询不到完成事件CQ 溢出。ibv_create_cq的深度要大于等于 SQ 加 RQ 的深度之和否则完成事件来不及取就丢了。另一个原因是 QP 进了错误状态ibv_query_qp看qp_state如果是IBV_QPS_ERR说明发生了不可恢复错误通常是远端访问了未注册的内存或者 rkey 过期。生产环境要注册错误处理回调ibv_create_qp时设qp_attr.qp_context和send_cq的cq_context出错时能拿到上下文。6. 用 rdma_cm 简化连接管理把精力留给业务逻辑手动管理 QP 状态迁移和 TCP 交换信息太繁琐librdmacm提供了rdma_cm接口把连接建立、QP 状态迁移、地址解析都封装了。核心流程是rdma_create_event_channel→rdma_create_id→rdma_resolve_addr→rdma_resolve_route→rdma_create_qp→rdma_connect/rdma_accept。事件驱动每个步骤完成会往 event channel 里投事件轮询处理即可。// rdma_cm 服务端简化流程 struct rdma_event_channel *ec rdma_create_event_channel(); struct rdma_cm_id *listen_id, *conn_id; rdma_create_id(ec, listen_id, NULL, RDMA_PS_TCP); rdma_bind_addr(listen_id, (struct sockaddr *)addr); rdma_listen(listen_id, 10); // 轮询事件 struct rdma_cm_event *event; while (rdma_get_cm_event(ec, event) 0) { if (event-event RDMA_CM_EVENT_CONNECT_REQUEST) { conn_id event-id; // 创建 QP注册 MR struct ibv_qp_init_attr qp_attr {0}; rdma_create_qp(conn_id, pd, qp_attr); // 接受连接 rdma_accept(conn_id, NULL); } rdma_ack_cm_event(event); }rdma_cm的好处是 QP 状态迁移由库自动完成你只需要在RDMA_CM_EVENT_ESTABLISHED事件后开始收发。rdma_create_qp的qp_attr里send_cq和recv_cq还是要自己建但 QP 状态不用管了。rdma_accept的第二个参数可以传私有数据用来交换 rkey 和地址。生产环境我一般用rdma_cm做连接管理用 verbs 做数据路径两者结合既省事又不损失性能。验证方法很简单用rdma_cm重写第 4 章的示例对比代码量和出错概率。我自己的习惯是先用ib_write_bw确认硬件和网络没问题再用rdma_cm写业务代码最后用perftest的延迟数据做基线业务代码跑出来差距超过 20% 就回头查 MR 注册和 CQ 深度。RDMA 这东西参数对了性能起飞参数错了还不如 TCP多测多对比别信玄学。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进