4. RDMA操作类型

RDMA 的核心价值,说白了就是让数据能绕过 CPU 和内核,直接在内存之间搬运。但搬运的方式有好几种,每种都有自己的脾气和适用场景。我个人习惯把这几种操作类型比作「快递服务」——有的需要双方确认签收,有的像直接拿钥匙开对方家门,还有的像在对方家里当场算个账再走。

嗯,咱们今天就把这四种操作类型彻底聊透。

4.1 Send / Recv:需要握手的双向通信

这是 RDMA 里最基础、也是最像传统 TCP 通信的模式。发送方主动发起 Send,接收方必须提前准备好 Recv WQE(工作请求),否则数据就丢了。

核心流程:

  • 接收方先 Post Recv,把接收缓冲区注册好。
  • 发送方 Post Send,数据直接写入接收方的内存。
  • 接收方完成 CQE 轮询,知道数据到了。

关键点:Send/Recv 是「一对一」的匹配关系。发送方发一次,接收方必须已经准备好一个 Recv WQE。如果接收方没提前 Post Recv,Send 会直接失败,或者数据被丢弃。

我在项目中遇到过一个问题:某个分布式存储系统,客户端频繁发送小 IO,服务端 Recv WQE 池子不够深,导致 Send 频繁返回错误。后来我们把 Recv WQE 队列深度从 64 调到 1024,问题就解决了。说白了,这就是典型的「生产者-消费者」速率不匹配。

Send 的三种变体

操作类型 行为描述 典型场景
Send 普通发送,数据写入接收方内存 控制消息、小数据块
Send with Immediate 发送数据时携带一个 4 字节的即时数 消息类型标识、序列号
Send with Invalidate 发送后自动使接收方指定 MR 的权限失效 安全场景,用完即弃

我的建议:控制面通信(比如连接建立、元数据交换)用 Send/Recv 最稳妥。数据面传输,能用 Read/Write 就别用 Send/Recv,延迟更低。

4.2 Read / Write:真正的「零拷贝」

这是 RDMA 最吸引人的地方。一方可以直接读写另一方的内存,不需要对方 CPU 参与。你想想看,这相当于你拿着钥匙直接进别人家拿东西,主人完全不用起身开门。

RDMA Read 流程:

  1. 发起方知道接收方的内存地址和 R_Key(远程密钥)。
  2. 发起方 Post Read WQE,指定本地缓冲区和远程缓冲区。
  3. 数据直接从远程内存拉到本地,接收方 CPU 完全不知情。
  4. 发起方轮询 CQE,确认数据已到达本地。

RDMA Write 流程:

  1. 发起方知道接收方的内存地址和 R_Key。
  2. 发起方 Post Write WQE,数据直接推送到远程内存。
  3. 接收方可以通过轮询或事件通知感知数据到达。

我曾经踩过的坑:RDMA Write 虽然快,但接收方如果没有主动轮询 CQ,数据写进去了也不知道。有一次调试一个存储系统,数据明明写到了对端内存,但应用层一直没反应,查了半天才发现接收方没开 CQE 通知。记住:Write 是「单向推送」,接收方必须自己想办法知道数据到了。

Read vs Write 对比

维度 RDMA Read RDMA Write
数据流向 远程 → 本地 本地 → 远程
接收方 CPU 参与 完全不需要 不需要(但需要感知)
典型延迟 ~1-2 µs ~0.5-1 µs
适用场景 拉取数据(如键值存储的 Get) 推送数据(如日志复制、镜像)

4.3 Atomic 操作:远程内存的原子运算

Atomic 操作是 RDMA 里比较「硬核」的部分。它允许你在远程内存上执行原子性的「比较-交换」或「取后加」操作,整个过程不需要远程 CPU 参与,而且保证并发安全。

为什么会需要这个?想象一下分布式锁的场景:多个节点同时尝试对一个远程变量做 CAS(Compare And Swap),如果没有硬件级别的原子操作,就必须靠消息传递来协调,延迟一下子就上去了。

两种 Atomic 操作:

  • Fetch and Add (原子加): 读取远程值,加上一个增量,写回去。返回原始值。
  • Compare and Swap (比较并交换): 比较远程值是否等于预期值,如果相等则交换为新值。返回原始值。

注意:Atomic 操作要求远程内存地址是 8 字节对齐的。不对齐的话,硬件直接报错。我刚开始写代码时没注意这个,调试了一整天,最后发现是地址没对齐。

我记得有一次做分布式事务系统,用 RDMA CAS 实现了一个全局计数器,用来分配事务 ID。性能比用 TCP 做 RPC 快了一个数量级,延迟从几十微秒降到了 2 微秒以内。嗯,这就是硬件原子操作的价值。

4.4 带即时数的操作:小数据搭便车

带即时数(Immediate Data)的操作,是 RDMA 里一个很巧妙的设计。它允许你在 Send 或 Write 时,额外携带一个 4 字节的整数。这个即时数会直接出现在接收方的 CQE 里,不需要额外的内存拷贝。

有什么用?

  • 消息类型标识:比如 0x01 表示「数据块」,0x02 表示「控制命令」。
  • 序列号:接收方通过 CQE 里的即时数判断消息顺序。
  • 小数据透传:如果数据本身只有 4 字节,直接用即时数传,连数据缓冲区都省了。

我的经验:带即时数的 Write 特别适合做「带外信号」。比如在批量数据传输过程中,用即时数告诉接收方「这批数据结束了」或者「下一个数据块的大小是 XXX」。接收方轮询 CQE 时就能拿到这个信息,不需要额外解析数据包。

4.5 知识结构总览

下面这张图把这四种操作类型的关系和适用场景梳理清楚了。我建议你把它存下来,写代码时对照着看。

RDMA 操作类型知识结构图 RDMA 操作类型全景图 RDMA 操作类型 Send / Recv Read / Write Atomic 操作 带即时数操作 特性 • 需要双方握手 • 接收方必须 Post Recv • 适合控制面通信 特性 • 真正的零拷贝 • 不需要对方 CPU 参与 • 延迟最低,吞吐最高 特性 • 硬件级原子性 • 支持 CAS / FetchAdd • 地址必须 8 字节对齐 特性 • 4 字节即时数 • 出现在 CQE 中 • 适合带外信号 选择原则:控制面用 Send/Recv,数据面用 Read/Write,同步用 Atomic,信号用 Immediate 注:所有操作都基于 RC(可靠连接)传输服务,保证数据有序、不丢、不重复

4.6 操作类型选择指南

写 RDMA 程序时,最常遇到的问题就是「该用哪种操作」。我总结了一个简单的决策流程,你写代码时可以参考:

  1. 是否需要对方 CPU 处理? 是 → 用 Send/Recv;否 → 继续往下看。
  2. 数据流向是拉还是推? 拉数据 → RDMA Read;推数据 → RDMA Write。
  3. 是否需要原子语义? 是 → Atomic CAS 或 FetchAdd。
  4. 是否需要带外小数据? 是 → 用带即时数的 Write 或 Send。

重要提醒:不要为了炫技而用 Read/Write。如果你的场景需要接收方对数据做校验或处理,老老实实用 Send/Recv。Read/Write 虽然快,但接收方完全不知道数据来了,你得自己设计通知机制。