﻿# RDMA 详解 —— 绕过 CPU 的远程内存直接访问（及它与 NUMA 的关系）

## 这篇文档是做什么的

> **一句话**：RDMA(Remote Direct Memory Access) 让一台机器的网卡**直接读写另一台机器的内存**，绕过 CPU、内核协议栈和多次数据拷贝，把网络延迟从几十微秒压到 1~2 微秒。它是**跨机**技术；[NUMA](/concepts/numa/numa.md) 是**本机**内存架构——两者不是一回事，但在高性能场景强相关（见第六节）。

## 可以回答什么问题

| 问题 | 答案在哪 |
|------|---------|
| RDMA 和传统 TCP/IP 性能差距有多大？为什么？ | §一——绕过 CPU、零拷贝、协议卸载，延迟从几十 us 降到 1~2 us |
| RDMA 有哪些实现方式？各有什么区别？ | §三——InfiniBand/RoCE/iWARP 三种，RoCE v2 最通用 |
| 程序中怎么用 RDMA？需要改代码吗？ | §四——QP/CQ/MR/PD 核心概念，libibverbs 编程 |
| RDMA 为什么和 NUMA 有关系？ | §六——网卡 PCIe 绑在某 NUMA 节点，跨节点 DMA 吃掉低延迟优势 |
| 怎么观测 RDMA 性能？ | §七——perftest、ib_read_bw、ib_write_bw 等工具 |

## 一、先看它解决什么：传统 TCP/IP 的开销

一次普通 socket 收发，数据要经过多层、CPU 全程参与：

```plantuml
@startuml
skinparam monochrome true
skinparam shadowing false
participant "应用 buffer" as App
participant "内核 socket buffer" as Kern
participant "协议栈 TCP/IP" as Stack
participant "网卡" as NIC
App  -> Kern  : ① 内存拷贝
Kern -> Stack : ② CPU 处理协议
Stack-> NIC   : ③ 发送
note over App, NIC
  全程 CPU 参与、多次内存拷贝
  上下文切换、中断开销
end note
@enduml
```

痛点：**多次内存拷贝 + CPU 全程参与协议处理 + 系统调用/中断开销**。在 40G/100G 网络下，CPU 光处理协议就可能打满，延迟也下不来。

RDMA 的思路是**彻底绕开这套**——网卡直接在两端应用内存间 DMA：

```plantuml
@startuml
skinparam monochrome true
skinparam shadowing false
participant "本端应用 buffer\n(已注册 MR)" as A
participant "本端网卡" as N1
participant "对端网卡" as N2
participant "对端应用 buffer\n(已注册 MR)" as B
A  -> N1 : DMA 直接读（不进内核、不拷贝）
N1 -> N2 : 网卡硬件卸载协议传输
N2 -> B  : DMA 直接写
note over A, B
  Kernel Bypass + Zero Copy
  CPU 几乎不参与
end note
@enduml
```

三个核心特征：

| 特征 | 含义 |
|------|------|
| **Kernel Bypass** | 数据路径不经过内核，用户态直接和网卡交互，省掉系统调用/上下文切换 |
| **Zero Copy** | 网卡 DMA 直接在应用注册的内存和网线间搬数据，没有内核↔用户态拷贝 |
| **Protocol Offload** | 传输协议在网卡硬件里跑，CPU 不参与拆包组包 |

结果：**微秒级延迟、极高带宽、极低 CPU 占用**。

## 二、三种 RDMA 实现（硬件路线）

| 实现 | 全称 | 说明 |
|------|------|------|
| **InfiniBand (IB)** | — | 专用网络（专用网卡+交换机），性能最强，HPC/超算首选；成本高、需独立组网 |
| **RoCE** | RDMA over Converged Ethernet | 在**以太网**上跑 RDMA，复用现有以太设备。v2 可路由（走 UDP/IP），数据中心主流；**依赖无损网络**（PFC/ECN 流控），配置是难点 |
| **iWARP** | internet Wide Area RDMA Protocol | 在 **TCP** 上跑 RDMA，能走普通以太网、可路由、对网络要求低；延迟不如前两者 |

> 选型速记：极致性能且能独立组网 → IB；数据中心复用以太网 → RoCE v2（主流）；只想在普通 TCP 网络上用 → iWARP。

## 三、核心概念（编程模型）

RDMA 编程和 socket 差别很大，几个必懂概念：

| 概念 | 作用 |
|------|------|
| **Verbs** | RDMA 的编程接口（类比 socket API），库为 `libibverbs`；`rdma-core` 是用户态软件栈 |
| **QP (Queue Pair)** | 队列对 = 发送队列(SQ) + 接收队列(RQ)，是通信的基本端点（类比连接）|
| **CQ (Completion Queue)** | 完成队列，操作完成后往这里放完成事件，应用去 poll（而非被中断打断）|
| **MR (Memory Region)** | 内存区域。**RDMA 前必须把要用的内存"注册"给网卡**（pin 住不让换出 + 建立虚拟地址↔物理地址映射，网卡 DMA 要用）|
| **PD (Protection Domain)** | 保护域，把 QP/MR 归组，做权限隔离 |
| **lkey / rkey** | 本地/远程访问 MR 的密钥，对端拿 rkey 才能直接读写你这块内存 |

两类操作语义：

- **双边操作 (SEND/RECV)**：像 socket，两端都要参与（发方 SEND、收方先挂好 RECV）。
- **单边操作 (RDMA READ/WRITE)**：**只有发起方参与**，直接读/写对端注册好的内存，对端 CPU **完全不知情**——这是 RDMA 最强大也最独特的能力（如分布式 KV、远程内存池）。

## 四、数据来源 / 如何观测

RDMA 不走内核网络栈，所以 `ss`/`netstat`/`tcpdump` 那套**基本看不到它**。观测靠专用工具：

- **设备与端口状态**：`/sys/class/infiniband/`（内核暴露的 RDMA 设备信息），或 `ibv_devices` / `ibv_devinfo`。
- **性能计数器**：`/sys/class/infiniband/<dev>/ports/<n>/counters/`（收发包、字节、错误）；`perfquery`(IB)。
- **工具**：`rdma`（iproute2 的 `rdma` 命令，看链路/资源）、`ibstat`、`ib_write_bw`/`ib_read_lat`（官方带宽/延迟基准测试）。

```bash
rdma link                 # 看 RDMA 设备和端口状态（类似 ip link）
ibv_devinfo               # 网卡能力、端口 state（PORT_ACTIVE 才可用）、链路速率
ibstat                    # IB/RoCE 设备状态概览
ib_write_bw / ib_read_lat # 官方 perftest：测带宽 / 测延迟（一端 server 一端 client）
cat /sys/class/infiniband/mlx5_0/ports/1/counters/port_xmit_data   # 原始计数器
```

## 五、典型应用场景

- **HPC / 超算**：MPI 底层用 RDMA 做节点间通信（[paper.md](/concepts/numa/paper.md) 第五节的 MPI 场景）。
- **分布式存储**：Ceph、NVMe-oF（NVMe over Fabrics）用 RDMA 把远程盘做得像本地盘一样快。
- **分布式数据库 / KV**：远程内存直读，绕过对端 CPU。
- **AI 训练**：GPU 集群多机多卡梯度同步（NCCL 走 RDMA / GPUDirect RDMA，网卡直接读写 GPU 显存）。
- **存算分离**：远程内存池、内存扩展。

## 六、RDMA 与 NUMA 的关系（关键）

**这就是它出现在本仓库的原因。** RDMA 追求极致低延迟，而 NUMA 的跨节点访问延迟足以**吃掉 RDMA 省下的那几微秒**，所以高性能 RDMA 部署必须做 NUMA 亲和。

### 为什么相关

RDMA 网卡插在某条 **PCIe 通道**上，而**每条 PCIe 通道归属某个 NUMA 节点**（由某颗 CPU 的 PCIe root complex 引出）。于是：

```plantuml
@startuml
skinparam monochrome true
skinparam shadowing false
skinparam componentStyle rectangle
package "Node0" {
  [CPU0] as CPU0
  [Mem0] as Mem0
  [RDMA 网卡] as NIC
}
package "Node1" {
  [CPU1] as CPU1
  [Mem1] as Mem1
  [GPU/其他] as GPU
}
CPU0 -right-> Mem0 : 本地
CPU1 -left->  Mem1 : 本地
CPU0 <-> CPU1 : QPI/UPI 互联
NIC  -up->    Mem0 : DMA 本地(快)
NIC  ..>      Mem1 : DMA 跨 QPI(慢!)\n抵消 RDMA 低延迟
note bottom of NIC : 网卡物理上挂在 Node0 的 PCIe
@enduml
```

如果 RDMA 的**内存区域(MR)、收发线程**和**网卡**不在同一个 NUMA 节点：网卡 DMA 访问那块内存要**跨 NUMA 节点走 QPI/UPI**，多出的延迟和带宽损耗，正好抵消 RDMA 相对 TCP 的优势。

### 怎么做 NUMA 亲和优化

1. **查网卡属于哪个 NUMA 节点**：

   ```bash

   cat /sys/class/infiniband/mlx5_0/device/numa_node    # 输出 0 表示网卡在 node0

   # 或 lspci 找到网卡 BDF 后：

   cat /sys/bus/pci/devices/0000:xx:00.0/numa_node

   ```

2. **把处理线程 + 内存绑到网卡所在节点**（用 [numactl](/tools/numa/numactl.md) / [libnuma](/concepts/numa/paper.md#45-什么时候该用-libnuma而不是-numactl)）：

   ```bash

   # 假设网卡在 node0：进程和内存都绑 node0

   numactl --cpunodebind=0 --membind=0 ./rdma_app

   ```

3. **MR 注册的内存也要落在网卡所在节点**——用 `numa_alloc_onnode()` 在网卡节点分配再注册，别让主线程在别的节点 malloc 后注册。
4. **中断亲和**：把 RDMA 网卡的中断也绑到同节点的核，减少跨节点。

> 一句话：**RDMA 把"跨机"延迟降到微秒级后，"跨 NUMA 节点"这种本机内的纳秒级开销就变得显眼了**——所以 RDMA 调优 = 传统网卡 offload 调优 + 严格的 NUMA 本地化。二者在"极致低延迟"这个目标上汇合。

## 七、关键指标速查

| 指标 | 缩写/英文 | 工具 | 正常值 | 异常信号 |
|------|----------|------|--------|---------|
| 端口状态 | Port State | `ibv_devinfo` | PORT_ACTIVE | DOWN / INIT → 链路故障 |
| 链路速率 | Link Speed | `ibv_devinfo` | 网卡规格 | 降速 → 物理层问题 |
| 收发包计数 | Xmit/Rcv Data | `/sys/class/infiniband/<dev>/ports/<n>/counters/` | 视业务 | 大量错误计数 → 物理层问题 |
| 完成队列溢出 | CQ Overrun | 同上 counters | 0 | > 0 → 应用处理跟不上 |
| 网卡 NUMA 节点 | NIC NUMA Node | `cat /sys/class/.../numa_node` | 与处理线程在同一节点 | 与处理线程跨节点 → 延迟翻倍 |
| 符号错误计数 | Symbol Errors | perfquery | 0 | > 0 → 物理链路质量差 |
| RDMA 带宽 | RDMA B/W | `ib_write_bw` | 接近线速 | 远低于线速 → 瓶颈在 PCIe/内存/NUMA |

## 八、常见排查方法与分析

### 8.1 RDMA 延迟高 → 检查 NUMA 亲和

```bash
# 基线：在同节点跑
numactl --cpunodebind=0 --membind=0 ./rdma_app
# 对比：跨节点跑
numactl --cpunodebind=1 --membind=1 ./rdma_app
# 网卡在 node0 时，跨节点延迟翻倍甚至更多
# 确认网卡归属
cat /sys/class/infiniband/mlx5_0/device/numa_node
# 确认进程内存实际落在哪
numastat -p <PID>
```

### 8.2 RDMA 链路不可用

```bash
ibv_devinfo → port_state 不是 PORT_ACTIVE
  → rdma link → 看物理链路状态
    → 检查网线/光模块 → 交换机端 port 配置
    → RoCE 场景：检查交换机 PFC/ECN 流控是否配置正确
```

### 8.3 RDMA 带宽远低于线速

```bash
ib_write_bw → 带宽只有线速的 30-50%
  → 检查 PCIe 带宽 → lspci -s <BDF> -vvv | grep -E "Width|Speed"
    → 是否插在了低带宽槽位
  → 检查 NUMA 亲和 → 同节点 vs 跨节点
  → 检查 MTU → 确认使用了最大 MTU（4096 或更大）
```

## 九、交叉引用

| 你想…… | 用什么 |
|--------|--------|
| 看普通 TCP/UDP 连接 | [ss](/tools/network/ss.md)（RDMA 流量它看不到）|
| 看 RDMA 设备/计数器 | `rdma`、`ibv_devinfo`、`/sys/class/infiniband/` |
| 确认网卡在哪个 NUMA 节点 | `cat /sys/class/.../numa_node`、[numactl -H](/tools/numa/numactl.md) |
| 把 RDMA 进程绑到网卡节点 | [numactl](/tools/numa/numactl.md) / [libnuma](/concepts/numa/paper.md#45-什么时候该用-libnuma而不是-numactl) |
| 理解跨节点为何变慢 | [numa.md](/concepts/numa/numa.md) |

> ⚠️ 平台提醒：RDMA 需要**专用硬件**（IB 网卡，或支持 RoCE/iWARP 的以太网卡，如 Mellanox/NVIDIA ConnectX 系列）和 `rdma-core` 软件栈。普通云主机、笔记本、macOS 没有 RDMA 设备，`ibv_devinfo` 会报无设备。RoCE 还依赖交换机侧的无损网络(PFC/ECN)配置。本篇是概念与优化思路，实操需在配好 RDMA 的环境里进行。

