| # UrmaTransport:基于 URMA 的远程内存 RPC | |
| UrmaTransport 是使用 openEuler | |
| [URMA](https://atomgit.com/openeuler/umdk)(Unified Remote Memory Access)SDK | |
| 实现的传输层。它是 | |
| [#3217](https://github.com/apache/brpc/discussions/3217) 中提出的路线 B, | |
| 与基于 OBMM 的 UBRing 传输(路线 A, | |
| [#3226](https://github.com/apache/brpc/issues/3226))互补,承担大包/跨节点 | |
| 高吞吐场景,共同构成路线 C(双后端)。 | |
| ## 技术背景 | |
| URMA 在 UMDK 支持的设备上提供 verbs 风格接口。当前实现创建可靠消息 | |
| (`URMA_TM_RM`)Jetty,并使用 CTP 传输路径;通过 | |
| `urma_post_jetty_send_wr` 提交发送 WR,通过 `urma_post_jfr_wr` 提交接收 | |
| WR。完成事件既可由 JFC 忙轮询获取,也可通过 JFCE 事件 fd 获取。 | |
| ## 编译配置 | |
| ### CMake 编译 | |
| ```bash | |
| # 带 URMA 支持编译 brpc | |
| cmake -B build -DWITH_URMA=ON | |
| make -C build -j$(nproc) | |
| # 编译 urma_performance 示例 | |
| cd example/urma_performance | |
| cmake -B build | |
| make -C build -j$(nproc) | |
| ``` | |
| `WITH_URMA=ON` 使用上游 UMDK 头文件进行编译。CMake 优先使用系统安装的 | |
| SDK;找不到头文件时,会参照 Mooncake 的 mock 构建方式下载固定版本的 | |
| UMDK commit,可通过 `DOWNLOAD_URMA_HEADERS=OFF` 禁止下载。找到 `liburma` 时使用 | |
| 真实硬件数据通路,否则链接 brpc 的 mock,使 URMA 代码和测试仍可在无硬件 | |
| 环境编译。`URMA_USE_MOCK` 默认为 `AUTO`;设为 `ON` 可强制使用 mock,设为 | |
| `OFF` 则要求找到 `liburma` 并排除 mock。 | |
| ### Make 编译 | |
| ```bash | |
| sh config_brpc.sh --headers=/usr/include --libs=/usr/lib \ | |
| --with-urma | |
| make -j4 | |
| # 强制使用 mock,或强制使用真实 liburma | |
| sh config_brpc.sh --headers=/usr/include --libs=/usr/lib \ | |
| --with-urma --with-urma-mock | |
| sh config_brpc.sh --headers=/usr/include --libs=/usr/lib \ | |
| --with-urma --without-urma-mock | |
| ``` | |
| 默认根据 `liburma` 是否存在自动选择;`--without-urma-mock` 要求配置路径中 | |
| 存在 `liburma`。 | |
| ### Bazel 编译 | |
| ```bash | |
| # 编译带 URMA 支持的 brpc | |
| bazel build --define=BRPC_WITH_URMA=true //:brpc | |
| # 仅展示关闭下载的开关;头文件仍须由工具链或本地 BUILD 目标声明 | |
| bazel build --define=BRPC_WITH_URMA=true \ | |
| --define=BRPC_DOWNLOAD_URMA_HEADERS=false //:brpc | |
| # 编译并运行 URMA 单元测试(使用 mock,不需要 URMA 硬件) | |
| bazel test --define=BRPC_WITH_URMA=true \ | |
| --define=BRPC_URMA_USE_MOCK=true //test:brpc_unittests | |
| # 使用真实 liburma;要求链接器可找到 -lurma | |
| bazel build --define=BRPC_WITH_URMA=true \ | |
| --define=BRPC_URMA_USE_MOCK=false //:brpc | |
| ``` | |
| Bazel 会自动获取固定 commit 的 UMDK 头文件,并通过 `@umdk//:urma_headers` | |
| 提供给 URMA 目标。设置 `BRPC_DOWNLOAD_URMA_HEADERS=false` 后,Bazel 不再依赖 | |
| 该外部仓库;实际构建仍需通过工具链或本地 BUILD 目标声明 | |
| `urma_api.h`、`urma_types.h` 和 `urma_ubagg.h`。仅通过 | |
| `--copt=-I/path` 指向工作区外目录,在默认 Bazel 沙箱下不会把未声明的头文件 | |
| 带入编译输入。设置 | |
| `BRPC_URMA_USE_MOCK=true` 强制加入 mock;设置为 `false` 则排除 mock 并链接 | |
| `liburma`。未指定时,URMA 构建默认使用 mock。 | |
| ## 使用 | |
| 通过在 channel / server 上设置 `socket_mode` 选择传输层: | |
| ```cpp | |
| // 客户端 | |
| brpc::ChannelOptions opt; | |
| opt.socket_mode = brpc::SOCKET_MODE_URMA; | |
| opt.protocol = "baidu_std"; // URMA 仅支持 baidu_std | |
| brpc::Channel channel; | |
| channel.Init("127.0.0.1:8003", &opt); | |
| // 服务端 | |
| brpc::ServerOptions sopt; | |
| sopt.socket_mode = brpc::SOCKET_MODE_URMA; | |
| server.Start(port, &sopt); | |
| ``` | |
| 若对端不支持 URMA(例如 TCP 客户端连接 URMA 服务端),在 4 字节 magic | |
| 握手后透明回退到 TCP,应用代码无需改动。 | |
| ## 架构 | |
| UrmaTransport 沿用与 `RdmaTransport` / `UBShmTransport` 一致的两层设计: | |
| ``` | |
| UrmaTransport : public Transport (urma_transport.{h,cpp}) | |
| +-- std::shared_ptr<TcpTransport> (回退路径) | |
| +-- urma::UrmaEndpoint* (URMA 数据路径) | |
| +-- UrmaState { URMA_ON, URMA_OFF, URMA_UNKNOWN } | |
| urma::UrmaEndpoint : public SocketUser (urma/urma_endpoint.{h,cpp}) | |
| +-- UrmaResource { jfc, jfce, jfr, jetty, remote_jetty, remote_seg } | |
| +-- 握手状态机(C/S 对称,在 TCP fd 上驱动) | |
| +-- 发送路径:urma_post_jetty_send_wr(URMA_OPC_SEND) | |
| +-- 接收路径:urma_poll_jfc -> HandleCompletion -> InputMessenger | |
| +-- 双窗口信用流控(_remote_rq_window_size / _sq_window_size) | |
| ``` | |
| ### 建链流程(双平面) | |
| 与 RDMA / UBRing 一致,控制面为 TCP,数据面为 URMA: | |
| 1. TCP 连接建立。 | |
| 2. `UrmaConnect::StartConnect` 起客户端握手 bthread。 | |
| 3. 双方在 TCP fd 上交换 `UrmaHello` 消息(v2 二进制 magic `URMA`, | |
| v3 protobuf magic `URM3`),携带本地 EID、jetty id、recv buffer 数量、 | |
| 以及扁平化的 buffer 池 segment。 | |
| 4. 双方先调用 `urma_import_seg` **再**调用 `urma_import_jetty`,为远端 EID | |
| 建立传输路径(TP)路由。跳过 `import_seg` 会导致首个 SEND 被硬件以 | |
| `URMA_CR_RNR_RETRY_CNT_EXC_ERR` 拒绝。 | |
| 5. 4 字节 ACK(`HELLO_ACK_URMA_OK = 0x1`)确认双方均要 URMA。 | |
| 6. 成功后 TCP fd 仅保留用于 epoll 生命周期和回退,数据走 URMA。 | |
| ### 内存管理 | |
| 申请一大段 `mmap` 内存,用 `urma_register_seg` 一次性注册,再切成固定大小 | |
| buffer(默认 8KB)。劫持 `butil::iobuf::blockmem_allocate` 使每个 IOBuf | |
| block 都由注册 segment 支撑,发送路径可直接从 IOBuf block refs 构建 | |
| `urma_sge_t`,无需逐消息注册(与 RDMA `block_pool` 设计一致)。用户注册 | |
| 内存通过 `urma::RegisterMemoryForUrma` / `DeregisterMemoryForUrma` 支持。 | |
| ## 配置 | |
| 所有 flag 使用 `urma_` 前缀(对标 RDMA 的 `rdma_` 前缀): | |
| | Flag | 默认 | 用途 | | |
| |------|------|------| | |
| | `--urma_use_polling` | false | 轮询 JFC 而非事件模式 | | |
| | `--urma_poller_num` | 1 | 每 bthread tag 的轮询器数(轮询模式) | | |
| | `--urma_disable_bthread` | false | 内联处理消息(不起 bthread) | | |
| | `--urma_sq_size` | 128 | 本地 JFS 深度 [16, 4096] | | |
| | `--urma_rq_size` | 128 | 本地 JFR 深度 [16, 4096] | | |
| | `--urma_cqe_poll_once` | 32 | 每次 `urma_poll_jfc` 的上限 | | |
| | `--urma_recv_zerocopy` | true | 大于 `--urma_zerocopy_min_size` 的接收零拷贝 | | |
| | `--urma_zerocopy_min_size` | 512 | 小于此值的接收拷贝 | | |
| | `--urma_device` | "" | URMA 设备名(空=首个) | | |
| | `--urma_max_sge` | 0 | 每 WR SGE 上限(0=设备上限) | | |
| | `--urma_bonding_mode` | 0 | bonding 模式:0=standalone,1=active-backup,2=balance | | |
| | `--urma_bonding_level` | 0 | bonding 层级:0=IODIE,1=port | | |
| | `--urma_prepared_jetty_cnt` | 8 | 预连接 Jetty+CQ 请求数量;会根据 `RLIMIT_NOFILE` 自动限制 | | |
| | `--urma_buffer_size` | 8192 | 池中每个 buffer 大小(字节) | | |
| | `--urma_buffer_count` | 65536 | 池中 buffer 数量 | | |
| | `--urma_poller_yield` | false | 忙轮询循环中主动让出 bthread | | |
| | `--urma_client_handshake_version` | 2 | 客户端握手版本(2=二进制,3=protobuf) | | |
| 设备名以 `bonding` 开头时,brpc 会在创建 context 后、创建 segment 和队列 | |
| 前配置 provider。默认 standalone+IODIE 配置与 UMDK 性能工具保持一致。 | |
| bonding 支持需要 provider 扩展头文件 `urma_ubagg.h`。 | |
| ## 与 UBRing 协同 | |
| UrmaTransport 推荐用于**大包和跨节点**高吞吐路径,而 UBRing | |
| (`SOCKET_MODE_UBRING`)对**小包和同机 IPC**最优(亚微秒、零系统调用)。 | |
| 混合负载可按服务选择传输层: | |
| | 场景 | 建议 `socket_mode` | | |
| |------|---------------------| | |
| | 同机 IPC | `SOCKET_MODE_UBRING` | | |
| | 跨节点小包(< 64KB) | `SOCKET_MODE_UBRING`(UBS-Mem)或 `SOCKET_MODE_URMA` | | |
| | 跨节点大包(>= 64KB) | `SOCKET_MODE_URMA` | | |
| | 传统 RoCE/IB 数据中心 | `SOCKET_MODE_URMA` 或 `SOCKET_MODE_RDMA` | | |
| 单连接内按包大小自动分流的方案(路线 C 方案 B)作为后续演进方向。 | |
| ## 限制 | |
| - 仅支持 `baidu_std` 协议(与 RDMA 一致)。SSL、RTMP、NSHEAD、MONGO 在 | |
| `ContextInitOrDie` 阶段拒绝。 | |
| - 硬件数据通路需要受支持的 UMDK provider 和 `liburma`。 | |
| - 当前实现面向 Linux。 |