3、RDMA核心概念:内存注册(MR)、队列对(QP)、完成队列(CQ)、工作请求(WR)

好,咱们今天聊点实在的。RDMA 这玩意儿,说白了就是让网卡直接读写你的应用内存,不用经过 CPU 和内核。但要做到这一点,硬件得知道「你的内存长什么样」、「数据该往哪送」、「送完了怎么通知你」。这就引出了四个核心概念:MR、QP、CQ、WR。

我刚开始接触 RDMA 时,觉得这些名词又长又绕。后来发现,你只要搞懂它们各自管什么事,整个 RDMA 的骨架就搭起来了。咱们一个一个拆。

3.1 内存注册(MR)—— 给网卡开个「通行证」

RDMA 网卡要直接读写你的应用内存,但操作系统可不会随便让硬件碰内存。你得先告诉内核:「这块内存,我授权给网卡用」。这个过程就叫内存注册(Memory Registration,MR)。

注册之后,网卡会拿到两个关键东西:

  • L_Key(本地键):本地操作时用的,比如把数据从这块内存发出去。
  • R_Key(远程键):远程机器要读/写这块内存时用的,相当于一个密码。

我个人习惯,在分配 MR 时尽量用大块连续内存。为什么呢?因为每个 MR 注册都会消耗网卡的硬件资源,MR 数量太多,性能反而会下降。

核心要点:MR 就是给网卡开了一张「内存通行证」。没有它,RDMA 根本动不了你的数据。

小技巧:我建议你在初始化阶段就把所有需要的内存一次性注册好,不要在热路径上频繁注册/注销 MR。否则延迟会飙升,你想想看,每次发数据前都要跟内核打个招呼,那还怎么快?

3.2 队列对(QP)—— RDMA 的「通信管道」

队列对(Queue Pair,QP)是 RDMA 通信的核心。它由两个队列组成:

  • 发送队列(SQ):放你要发出去的工作请求。
  • 接收队列(RQ):放你准备接收的工作请求。

为什么叫「对」?因为通信双方各有一个 QP,它们之间建立连接后,数据就在这两个 QP 之间流动。你可以把 QP 想象成一根水管,一头接你的应用,另一头接远程的应用。

QP 有三种类型:

类型 特点 适用场景
RC(可靠连接) 一对一,可靠,保序 大多数场景,比如存储、数据库
UC(不可靠连接) 一对一,不可靠,不保序 对丢包不敏感的场景
UD(不可靠数据报) 多对多,不可靠,最大 MTU 多播、广播、控制面通信

我在项目中遇到过一个问题:QP 数量太多,导致网卡内存耗尽。当时我们一个节点要跟几百个对端通信,每个对端建一个 RC QP,结果网卡直接罢工了。后来改成 UD + 应用层重传,才把问题解决。嗯,这里要注意:QP 不是越多越好,要根据场景选类型。

避坑指南:我曾经见过有人把 QP 的发送队列深度设得特别大,觉得这样能提高吞吐。结果呢?内存占用暴涨,延迟反而变高了。QP 的队列深度要跟你的业务模型匹配,不是越大越好。

3.3 完成队列(CQ)—— 操作完成的「消息中心」

你发了一个工作请求,怎么知道它执行完了?轮询?太浪费 CPU。中断?延迟又高。RDMA 用了一个折中方案:完成队列(Completion Queue,CQ)。

CQ 里存放的是完成事件(Completion Event)。当你的发送或接收操作完成后,网卡会把结果写进 CQ。你的应用只需要从 CQ 里取事件,就知道操作成功了还是失败了。

这里有个关键点:一个 CQ 可以服务于多个 QP。我习惯把同一类业务的 QP 挂到同一个 CQ 上,这样管理起来方便,轮询线程也少。

核心要点:CQ 是异步通知机制的核心。它让你不用死等每个操作完成,而是批量处理完成事件,效率高得多。

3.4 工作请求(WR)—— 告诉网卡「干什么活」

工作请求(Work Request,WR)就是你发给 QP 的指令。它告诉网卡:

  • 从哪块内存读数据(源地址)
  • 写到哪块内存(目标地址)
  • 数据有多长
  • 操作类型(发送、接收、读、写、原子操作)

WR 的结构大致如下:

struct ibv_send_wr {
    uint64_t        wr_id;      // 用户自定义 ID,用于匹配完成事件
    struct ibv_sge  *sg_list;   // 散列表,描述数据在内存中的位置
    int             num_sge;    // 散列表项数
    enum ibv_wr_opcode opcode;  // 操作类型
    // ... 其他字段
};

你想想看,WR 就是你把任务清单交给网卡,网卡照着清单干活。干完了,往 CQ 里扔一个完成事件,告诉你「活干完了」。

个人经验:我建议你在 WR 的 wr_id 里编码一些业务信息,比如请求序号、连接 ID。这样从 CQ 取完成事件时,不用再查上下文,直接就能知道是哪个请求完成了。这个小技巧能省不少 CPU 开销。

3.5 它们怎么配合?一张图看懂

说了这么多,咱们用一张图把 MR、QP、CQ、WR 的关系串起来:

应用层(你的代码) 内存注册(MR) L_Key / R_Key 工作请求(WR) 操作类型 + 数据描述 队列对(QP) 发送队列(SQ) 接收队列(RQ) 完成队列 (CQ) RDMA 网卡硬件(HCA) 注册内存 提交任务 入队 引用 完成事件 硬件执行 通知

流程很简单:

  1. 你先注册一块内存(MR),拿到钥匙。
  2. 构造一个工作请求(WR),告诉网卡要干什么活。
  3. 把 WR 扔进队列对(QP)的发送队列或接收队列。
  4. 网卡从 QP 里取 WR,直接读写你的 MR 内存。
  5. 干完了,往完成队列(CQ)里写一个完成事件。
  6. 你的应用从 CQ 里取事件,知道活干完了。

说白了,MR 是「地盘」,QP 是「管道」,WR 是「任务单」,CQ 是「公告栏」。四个东西配合起来,RDMA 才能做到零拷贝、低延迟、少 CPU 干预。

总结一下:搞懂这四个概念,RDMA 的大门就打开了。后面咱们聊的每一个协议、每一种操作,都离不开它们。你先把这张图刻在脑子里,后面学起来会轻松很多。


公众号:蓝海资料掘金营,微信 deep3321