UrmaTransport:基于 URMA 的远程内存 RPC

UrmaTransport 是使用 openEuler URMA(Unified Remote Memory Access)SDK 实现的传输层。它是 #3217 中提出的路线 B, 与基于 OBMM 的 UBRing 传输(路线 A, #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 编译

# 带 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 编译

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 编译

# 编译带 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 选择传输层:

// 客户端
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_pollingfalse轮询 JFC 而非事件模式
--urma_poller_num1每 bthread tag 的轮询器数(轮询模式)
--urma_disable_bthreadfalse内联处理消息(不起 bthread)
--urma_sq_size128本地 JFS 深度 [16, 4096]
--urma_rq_size128本地 JFR 深度 [16, 4096]
--urma_cqe_poll_once32每次 urma_poll_jfc 的上限
--urma_recv_zerocopytrue大于 --urma_zerocopy_min_size 的接收零拷贝
--urma_zerocopy_min_size512小于此值的接收拷贝
--urma_device""URMA 设备名(空=首个)
--urma_max_sge0每 WR SGE 上限(0=设备上限)
--urma_bonding_mode0bonding 模式:0=standalone,1=active-backup,2=balance
--urma_bonding_level0bonding 层级:0=IODIE,1=port
--urma_prepared_jetty_cnt8预连接 Jetty+CQ 请求数量;会根据 RLIMIT_NOFILE 自动限制
--urma_buffer_size8192池中每个 buffer 大小(字节)
--urma_buffer_count65536池中 buffer 数量
--urma_poller_yieldfalse忙轮询循环中主动让出 bthread
--urma_client_handshake_version2客户端握手版本(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
同机 IPCSOCKET_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。