本页课程原文(点开阅读)

RDMA 远程直接内存访问

课程(6 节,按顺序学)

第 1 课 · 先数清楚普通网络那条路有多长

入门 · 约 8 分钟

本课目标:把 TCP 发一个消息要付的代价一项项列出来——不列清楚就体会不到 RDMA 省了什么。

一次 write() 到底发生了什么

应用调一次 write(),数据要走这么长一条路:

① 系统调用:陷入内核,用户态/内核态切换
② 拷贝 #1:把数据从用户缓冲区拷进内核 socket 缓冲区(CPU 亲自搬)
③ 内核协议栈:分段、算校验和、填 TCP/IP 头、查路由(CPU 在算)
④ DMA 给网卡 → 上线缆
⑤ 对端网卡收到,DMA 进内核缓冲区
⑥ 中断 + 软中断:把对端 CPU 打断,拉进来干活
⑦ 对端内核协议栈:校验、排序、重组、回 ACK
⑧ 拷贝 #2:内核缓冲区 → 对端用户缓冲区
⑨ 对端 read() 返回,又一次上下文切换

★ 数一下:2 次拷贝、2 次系统调用、两边内核都要过、对端 CPU 全程被占用。

★ 哪些代价跟数据量成正比

拷贝和协议栈处理是按字节算的——数据越大越疼。
系统调用和中断是固定开销——消息越小,它们占的比例越大。

★★ 记住这个区别,第 6 课判断「该不该上 RDMA」要用到。

动手:先看 TCP 那条路

去流量仿真页点一下,先只看左边那一栏(TCP)。
★ 注意画布上两边的内核框都亮了,以及右边那一列「占 CPU 的阶段数」。

实验重点

  • ★ TCP 发一个消息:2 次拷贝 + 2 次系统调用 + 两边内核都要过
  • ★ 对端 CPU 全程被拖进来(中断 + 协议栈 + 拷贝)
  • 拷贝/协议栈按字节算,系统调用/中断是固定开销

第 2 课 · ★★★ RDMA 抄了哪三条近道

进阶 · 约 12 分钟

本课目标:零拷贝、内核旁路、硬件卸载——这三件事凑起来才是 RDMA。

★★★ 近道一:零拷贝(Zero Copy)

网卡直接从应用自己的内存里 DMA 读数据,读完直接发。

没有「先拷到内核再发」这一步 —— 拷贝次数 2 → 0。

★ 前提:那块内存必须事先注册过(第 4 课细讲)。

★★★ 近道二:内核旁路(Kernel Bypass)

发送不再是「调 write() 陷入内核」,而是:
应用在用户态直接往网卡的队列(QP)里塞一个 WQE,然后敲一下门铃。

那个队列是 mmap 到用户态的内存,所以压根没有系统调用——
系统调用 2 → 0,上下文切换也一起省了。

★ 收完成通知(CQE)也是在用户态轮询,同样不陷内核。

★★★ 近道三:硬件卸载(Transport Offload)

分段、序号、校验、重传、拥塞控制——这些传输层的活全下沉到网卡硬件。

TCP 是内核软件在算,要花 CPU;RDMA 是网卡在算,CPU 完全腾出来。

★★ 所以 RDMA 的收益不只是延迟低,更重要的是「同样的流量,CPU 几乎不用管」——这在存储和 AI 训练里比延迟更值钱。

★ 有一段是完全一样的

★★ 注意画布:网卡 → 交换机 → 网卡 这一段,TCP 和 RDMA 走的是同一条线、花的是同样的时间。

差别从来不在网线上,而在「数据在机器内部多走了几段、多占了多少 CPU」。这句话是这个模块最该记住的一句。

动手:把两条路摆在一起

去流量仿真页,看画布上的两条路径和右边的对比表。

★ 重点看三行:内存拷贝次数、系统调用次数、传输层协议处理在哪。

实验重点

  • ★★★ 零拷贝:网卡直接读写应用内存,拷贝 2 → 0
  • ★★★ 内核旁路:用户态直接操作网卡队列,系统调用 2 → 0
  • ★★★ 硬件卸载:传输层从内核软件搬到网卡硬件,CPU 腾出来
  • ★★ 差别不在网线上 —— 线缆那一段两者完全一样

第 3 课 · ★★★ 单边 vs 双边:对端 CPU 参不参与

进阶 · 约 10 分钟

本课目标:这是 RDMA 最反直觉、也最有用的一点。

★★★ 单边(One-sided):WRITE / READ

本端在 WQE 里写好「对端的内存地址 + rkey」,网卡直接把数据写进对端内存(WRITE),或者从对端内存读回来(READ)。

★★★ 全程对端 CPU 一点都不参与 —— 对端的应用甚至不知道这件事发生过。

★ 这就是「单边」的含义:一边发起,另一边完全被动。

★★ 双边(Two-sided):SEND / RECV

更像传统的收发:
· 对端必须事先 post 一个 RECV,告诉网卡「收到的数据放这儿」
· 数据到了之后,对端 CQ 里会出现一个 CQE,对端应用轮询到才知道

★ 所以双边的对端 CPU 轻度参与了——但注意:
它依然零拷贝、依然不进内核协议栈。省的东西大部分还在。

★ 各自适合什么

· 单边 WRITE/READ:适合「我知道该往哪写」的场景——分布式内存、参数服务器、存储写入。对端 CPU 完全解放。
· 双边 SEND/RECV:适合「要通知对方发生了什么」的场景——消息传递、RPC 的控制面。语义更自然。

★★ 现网常见的组合:用双边 SEND 传小的控制消息,用单边 WRITE 搬大块数据。

动手:对比对端 CPU 那一行

分别用 RDMA WRITE 和 RDMA SEND/RECV 各跑一遍,
看「对端 CPU 参与」那一行从完全不参与变成轻度参与,
以及画布上 SEND 会碰到对端的应用框、WRITE 压根不碰。

实验重点

  • ★★★ 单边 WRITE/READ:对端 CPU 完全不参与,对端应用不知情
  • ★★ 双边 SEND/RECV:对端要 post RECV + 收 CQE,轻度参与
  • ★ 双边依然零拷贝、依然不进内核 —— 省的大部分还在
  • 现网常见:小控制消息用双边,大块数据用单边

第 4 课 · ★★ 代价:三个躲不掉的前置条件

进阶 · 约 10 分钟

本课目标:RDMA 不是「装个网卡就快」——前提一个都不能少。

★★ 前提一:内存必须先注册(Memory Region)

网卡要绕过 CPU 直接读写内存,就必须事先知道「哪块内存可以碰」。

注册(ibv_reg_mr)会做两件事:
· 把这块内存锁住(pin),不许操作系统换出去
· 生成一对 key:lkey(本端用)和 rkey(给对端用)

★ 没注册 → 没有 key → 网卡无权访问 → 一个字节都发不出去。
★★ 而且注册本身是慢操作,所以现网都是启动时一次注册好、反复用,或者维护一个已注册内存池。

★★ 前提二:QP 必须推到 RTS

Queue Pair 要一路推状态:RESET → INIT → RTR → RTS,只有到了 RTS 才能真正发数据。

★★★ 前提三:得先用普通网络交换信息

推 QP 状态需要对端的 QP 号、GID、rkey——而这些信息 RDMA 自己传不了(连接还没建起来呢)。

★★★ 所以现实是:RDMA 的连接建立,靠的是普通 TCP(或者 RDMA CM,底下同样走普通网络)。

★ 这个事实很多人第一次听会觉得别扭,但它非常重要:RDMA 是「快速数据通道」,不是「取代整个网络栈」。

动手:把前提拆掉看看

当前场景 ③ 忘了注册内存。跑一遍——
★ RDMA 直接失败在第一步,而同样条件下 TCP 照发。

再去场景 ④ QP 没到 RTS 跑一遍,看另一种失败。

实验重点

  • ★★ 内存必须先注册(pin + lkey/rkey),而且注册是慢操作
  • ★★ QP 必须推到 RTS 才能发
  • ★★★ 推状态要的信息得先用普通 TCP 交换 —— RDMA 的连接建立靠普通网络
  • ★ 这三条是 RDMA 的代价:TCP 什么都不用准备,插上网线就能发

第 5 课 · ★★★ 为什么 RoCE 必须配无损网络

排障 · 约 12 分钟

本课目标:同样丢 1% 的包,TCP 只掉一点,传统 RoCE 掉一大截——原因在重传策略。

TCP 丢包:只补丢掉的那几个

TCP 有快速重传 + SACK:接收方能明确告诉发送方「我缺第 5、第 9 个包」,发送方只补这两个。

再加上拥塞窗口减半慢慢涨回来 —— 吞吐掉一截,但是平滑降级。

★★★ 传统 RoCE 丢包:go-back-N,后面全部重发

传统 RoCEv2 的硬件重传是 go-back-N:
丢了第 5 个包,那么第 5 个之后已经发出去的全部都要重发。

★★ 在途窗口越大,浪费越狠。100G 链路 + 现网带排队的往返,在途可能有几百个包 —— 丢一个就白跑几百个。

⚠️ 一句公平话:较新的网卡已经支持选择性重传(只补丢的那个),情况比 go-back-N 时代好很多。但「RoCE 需要近似无损」这条工程结论目前依然成立。

★★★ 所以要把网络做成「不丢包」

两件事一起上:

· PFC(Priority Flow Control):交换机缓冲快满时,往上游发暂停帧,先把发送方压住而不是把包丢掉。
⚠️ 但 PFC 用猛了会有副作用:队头阻塞、PFC 风暴、甚至死锁。
· ECN + DCQCN:交换机在还没满的时候就给包打个标记,接收方回通知,发送方主动降速 —— 让 PFC 尽量不用触发。

★★ 正确的姿势是以 ECN/DCQCN 为主、PFC 只做最后兜底。

动手:亲眼看保持率的差距

当前场景 ⑤ 1% 丢包 + 没开无损。跑一遍,看「丢包后的吞吐保持率」那一行。

然后在仿真页展开实验参数、把「无损网络」打开,再跑一遍 —— 保持率回到 100%。

实验重点

  • TCP + SACK:只补丢的那几个,平滑降级
  • ★★★ 传统 RoCE go-back-N:丢一个包,它后面整个窗口全部重发
  • ★★★ 解法:ECN/DCQCN 为主 + PFC 兜底,把网络做成近似无损
  • ⚠️ PFC 用猛了会队头阻塞/风暴/死锁,不能只靠它
  • ★ 诚实的说法是「RoCE 掉得比 TCP 狠得多」,不是「RoCE 一定比 TCP 慢」

第 6 课 · ★ 什么时候该上 RDMA,什么时候不该

进阶 · 约 10 分钟

本课目标:不吹不黑:把适用条件和门槛都摆出来。

★ 收益最大的三类场景

· 存储:NVMe-oF、分布式文件系统 —— 大块数据搬来搬去,省下的拷贝和 CPU 直接变成吞吐
· AI / HPC:多卡多机做梯度同步(GPUDirect RDMA 还能让网卡直接读写显存,连主机内存都不过)
· 低延迟中间件:分布式缓存、金融交易 —— 吃的是那几微秒

★★ 什么时候别上

· 跨广域网:RDMA 假设网络近似无损、延迟稳定,公网完全不满足
· 小流量、连接数极多:每个连接一个 QP,QP 是硬件资源,几万条连接扛不住;而且注册内存要 pin,内存开销也在
· 团队没有能力运维无损网络:PFC/ECN 调不好,性能可能比 TCP 还差、还更难查
· 应用改造成本:verbs 编程模型跟 socket 完全不一样,不是换个库就行

★ 中间选项:别忘了还有别的路

在「原生 TCP」和「全面 RDMA」之间还有台阶:

· io_uring / 零拷贝 send:省掉一部分拷贝和系统调用,改造成本低得多
· DPDK / 用户态网络栈:内核旁路,但传输层还得自己管
· 智能网卡卸载 TCP:保留 socket 语义,把协议处理挪到硬件

★★ 先量清楚瓶颈到底是「拷贝」「系统调用」还是「协议栈 CPU」,再决定要不要付 RDMA 那份运维成本。

动手:看消息大小怎么改变结论

当前场景 ⑥ 大消息 1MB。跑一遍,再把消息调回 4KB 跑一遍。

★ 小消息:RDMA 赢在延迟(省掉的固定开销占比大)
★ 大消息:延迟倍数被传输时间摊薄,但省下的 CPU 和内存带宽更值钱

实验重点

  • ★ 适合:存储、AI/HPC、低延迟中间件 —— 大块数据或者极致延迟
  • ★★ 不适合:跨广域网、海量小连接、没能力运维无损网络
  • ★ 中间台阶:io_uring / DPDK / 智能网卡卸载
  • ★★ 先量清楚瓶颈在哪,再决定要不要付这份运维成本
冀ICP备2026021556号-2|冀公网安备13068302000206号