2、RDMA基础:RDMA技术原理、InfiniBand与RoCEv2协议栈、RDMA操作类型(Send/Recv、Read/Write、原子操作)
2.1 核心原理:从“拷贝两次”到“内核旁路”
传统以太网TCP/IP通信中,数据从网卡到用户进程需要经过内核协议栈处理,并经历多次内存拷贝。以一次服务端接收数据为例:数据先由网卡DMA到内核缓冲区,CPU参与协议解析后拷贝到socket接收队列,再经系统调用read()拷贝到用户态缓冲区。整个过程涉及两次数据拷贝和多次上下文切换,CPU开销巨大,延迟通常在数十微秒量级。
RDMA(Remote Direct Memory Access)的核心思想是内核旁路(Kernel Bypass)与零拷贝(Zero-Copy)。它允许应用程序直接与网卡上的硬件队列交互,绕过操作系统内核,由网卡硬件负责数据的封装、传输和确认。发送方网卡直接从用户态内存读取数据,接收方网卡直接将数据写入用户态指定的内存区域,全程无需CPU参与数据搬运。
RDMA三大核心技术支柱:
- 内核旁路:应用通过verbs API直接访问网卡硬件队列(QP),绕过内核协议栈。
- 零拷贝:网卡硬件DMA直接访问用户态内存,消除内核缓冲区与用户缓冲区之间的数据复制。
- 卸载(Offload):传输层可靠性、流控、报文封装等由网卡硬件完成,释放CPU资源。
2.2 InfiniBand与RoCEv2协议栈
RDMA并非单一技术,而是三种主流网络协议的总称。它们共享相同的verbs编程接口,但底层传输机制和物理层实现不同。
| 特性 | InfiniBand | RoCEv2 | iWARP |
|---|---|---|---|
| 物理层 | 专用IB链路(需IB交换机) | 标准以太网(支持IP路由) | 标准以太网 |
| 网络层 | IB自有网络层(LID路由) | IP/UDP封装(端口4791) | TCP/IP |
| 传输层 | IB传输层(硬件实现) | IB传输层(硬件实现) | TCP卸载引擎 |
| 延迟 | 最低(~0.5μs) | 低(~1.5μs) | 较高(~5μs) |
| 部署成本 | 高(专用设备) | 低(利用现有以太网) | 中 |
当前业界最主流的是InfiniBand(高性能计算集群首选)和RoCEv2(超大规模数据中心和AI训练集群广泛采用)。RoCEv2将IB的报文封装在UDP/IPv4或IPv6中,使得RDMA流量可以跨二层/三层网络路由,大幅提升了部署灵活性。
RoCEv2报文格式
+------------------+------------------+------------------+------------------+
| Ethernet MAC | IPv4/IPv6 头 | UDP头(端口4791)| IB BTH+报文 |
+------------------+------------------+------------------+------------------+
↓ ↓ ↓ ↓
二层寻址 三层路由 UDP多路复用 RDMA语义(RC/UC/UD)
RoCEv2的UDP目的端口固定为4791,用于区分普通以太网流量。IB的Base Transport Header(BTH)中包含了OpCode(操作码)、目的QP号、分组序号(PSN)等关键字段,硬件据此完成可靠连接(RC)模式下的排序、重传和确认。
2.3 RDMA操作类型详解
RDMA提供了多种数据传输语义,理解这些操作类型是掌握原子操作与锁机制的前提。所有操作均通过提交工作请求(Work Request, WR)到发送队列(SQ)或接收队列(RQ)来触发,网卡硬件异步执行完成后在完成队列(CQ)中产生完成事件(CQE)。
2.3.1 Send / Recv(双边操作)
类似传统消息传递,但由硬件完成。发送方调用ibv_post_send()提交Send WR,接收方必须预先提交Recv WR到RQ中,否则数据会被丢弃(RC模式)或发送方收到错误。适用于控制消息、短消息、动态长度数据的传输。
2.3.2 Read / Write(单边操作)
这是RDMA最具特色的能力。发送方(发起端)直接指定对端内存地址(虚拟地址+R_key),由硬件发起RDMA Read(从对端读数据到本地)或RDMA Write(将本地数据写入对端)。接收方CPU完全无感知,无需预置Recv缓冲区。适用于大数据块传输、流水线并行、参数服务器更新等场景。
关键区别:Send/Recv是“双方协作”模式,接收方必须提前准备好缓冲区;Read/Write是“单方主导”模式,发起方直接操作对端已注册的内存区域。原子操作则属于单边操作的延伸,但粒度更小(8字节)。
2.3.3 原子操作(Atomic Operations)
RDMA硬件支持两种原子操作原语,均针对8字节对齐的内存地址执行:
- FetchAndAdd(原子加):
old = *addr; *addr += add_val; return old; - CompareAndSwap(比较交换):
old = *addr; if (old == compare_val) *addr = swap_val; return old;
这两种操作由网卡硬件在目标节点的内存上原子执行,不经过对端CPU,且在整个执行过程中保证互斥。它们是构建分布式锁、计数器、无锁队列的基础构件。
2.4 案例:基于原子操作实现分布式互斥锁
假设集群中有N个节点需要竞争一把锁。我们在节点0(锁服务器)上分配一个8字节内存区域作为锁变量,初始值为0(表示未锁定)。
// 锁获取(客户端节点发起)
struct ibv_atomic_wr atomic_wr = {0};
atomic_wr.remote_addr = lock_server_addr; // 锁变量地址
atomic_wr.compare_add = 1; // FetchAndAdd的增量
atomic_wr.swap = 0; // CAS的交换值(此处未用)
atomic_wr.rkey = lock_server_rkey; // 远程内存访问密钥
// 使用FetchAndAdd尝试获取锁
old_val = ibv_post_atomic(qp, &atomic_wr, IBV_WR_ATOMIC_FETCH_AND_ADD);
if (old_val == 0) {
// 获取成功,进入临界区
} else {
// 锁已被占用,重试或自旋等待
}
当多个节点同时执行FetchAndAdd时,网卡硬件保证每个节点获得唯一的、递增的old_val。只有返回0的节点获得锁,其他节点根据返回的序号可以设计排队机制(如Ticket Lock)。释放锁时,获得锁的节点执行一次原子写操作将锁变量清零。
2.5 常见误区与复盘
误区一:认为RDMA Read/Write不需要注册内存。实际上,所有被RDMA访问的内存区域(无论是本地还是远程)都必须提前通过ibv_reg_mr()注册,并获得lkey/rkey。注册过程会锁定物理内存页并建立DMA映射。
误区二:混淆原子操作与CPU原子指令。RDMA原子操作是跨节点的原子性,由网卡硬件在目标内存上执行,而非目标节点的CPU指令。它解决的是分布式系统中的并发竞争问题,与单机多线程的__sync_fetch_and_add有本质区别。
误区三:忽略原子操作的性能代价。虽然原子操作避免了CPU参与,但网卡执行原子操作时需要对目标内存进行锁定(Lock),这会导致同一内存地址上的后续访问被阻塞。在高并发场景下,原子操作可能成为性能瓶颈,应尽量使用RDMA Write批量更新,减少原子操作频率。