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 流程:
- 发起方知道接收方的内存地址和 R_Key(远程密钥)。
- 发起方 Post Read WQE,指定本地缓冲区和远程缓冲区。
- 数据直接从远程内存拉到本地,接收方 CPU 完全不知情。
- 发起方轮询 CQE,确认数据已到达本地。
RDMA Write 流程:
- 发起方知道接收方的内存地址和 R_Key。
- 发起方 Post Write WQE,数据直接推送到远程内存。
- 接收方可以通过轮询或事件通知感知数据到达。
我曾经踩过的坑: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 知识结构总览
下面这张图把这四种操作类型的关系和适用场景梳理清楚了。我建议你把它存下来,写代码时对照着看。
4.6 操作类型选择指南
写 RDMA 程序时,最常遇到的问题就是「该用哪种操作」。我总结了一个简单的决策流程,你写代码时可以参考:
- 是否需要对方 CPU 处理? 是 → 用 Send/Recv;否 → 继续往下看。
- 数据流向是拉还是推? 拉数据 → RDMA Read;推数据 → RDMA Write。
- 是否需要原子语义? 是 → Atomic CAS 或 FetchAdd。
- 是否需要带外小数据? 是 → 用带即时数的 Write 或 Send。
重要提醒:不要为了炫技而用 Read/Write。如果你的场景需要接收方对数据做校验或处理,老老实实用 Send/Recv。Read/Write 虽然快,但接收方完全不知道数据来了,你得自己设计通知机制。