☰
brpc 的 RDMA 支持:构建、实现原理与参数配置指南
2026/10/9 1:25:00 网站建设 项目流程

【免费下载链接】brpc

brpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".

项目地址:https://gitcode.com/gh_mirrors/brpc3/brpc
点击查看免费下载

brpc 是 C++ 实现的工业级 RPC 框架,其 RDMA(Remote Direct Memory Access)模块让应用可以在支持 InfiniBand / RoCE 的网卡上,以零拷贝、滑动窗口流控等机制进行高性能数据传输。本文以仓库文档 docs/en/rdma.md 为核心,结合 src/brpc/rdma 目录下的源码与 example/rdma_performance 示例,系统讲解 RDMA 的编译接入、基于 verbs API 的底层实现,以及全部可调参数的语义与默认值,帮助你完成从「启用 RDMA」到「调优内存与窗口」的完整落地。

一、编译与构建:如何启用 RDMA 支持

RDMA 依赖网卡驱动与硬件支持,brpc 目前只在 Linux 上验证过 RDMA 的构建。仓库同时提供了 config_brpc、CMake、Bazel 三种构建方式,开启宏开关后即可把 RDMA 模块编译进 brpc。

1.1 使用 config_brpc 构建

sh config_brpc.sh --with-rdma --headers="/usr/include" --libs="/usr/lib64 /usr/bin" make cd example/rdma_performance # example for rdma make

--with-rdma会为编译过程定义BRPC_WITH_RDMA宏,--headers与--libs用于指定包含头文件(含infiniband/verbs.h)与链接库(如 libibverbs)的路径。

1.2 使用 CMake 构建

mkdir bld && cd bld && cmake -DWITH_RDMA=ON .. make cd example/rdma_performance # example for rdma mkdir bld && cd bld && cmake .. make

CMake 方案通过-DWITH_RDMA=ON开启 RDMA,示例目录自身也需要单独走一遍 CMake 生成与编译。

1.3 使用 Bazel 构建

# Server bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_server # Client bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_client

1.4 验证:运行 RDMA 性能示例

示例目录 example/rdma_performance 中,server.cpp 默认监听 8002 端口,client.cpp 提供压测入口。两个程序都定义了DEFINE_bool(use_rdma, true, "Use RDMA or not"),并分别把该标志写入ServerOptions.use_rdma与ChannelOptions.use_rdma(见 server.cpp 与 client.cpp),是观察 RDMA 生效与否最直接的参考实现。

二、核心实现:RdmaEndpoint 与 verbs 数据通路

2.1 Socket 之上叠加 RdmaEndpoint

与 TCP 使用 socket API 不同,RDMA 通过 verbs API 直接驱动网卡。brpc 仍然复用brpc::Socket抽象:当用户把ChannelOptions.use_rdma或ServerOptions.use_rdma置为 true 时,对应 Socket 会创建RdmaEndpoint(实现见 src/brpc/rdma/rdma_endpoint.cpp,接口见 src/brpc/rdma/rdma_endpoint.h)。

启用 RDMA 后,发送方不再把数据写入 TCP fd,而是通过 verbs API 把待发送数据 post 到 RDMA 队列对(QP,Queue Pair)上;接收方则由RdmaEndpoint通过 verbs API 从完成队列(CQ,Completion Queue)取出完成事件。值得注意的事件驱动细节是:CQ 关联了一个专用 fd(completion channel),该 fd 会被注册进 brpc 的 EventDispatcher,事件到达后由 RdmaEndpoint::PollCq 统一处理,随后才交由InputMessenger解析 RPC 消息。

2.2 RC 模式与基于 TCP 的握手(handshake)

brpc 使用 RDMA 的 RC(Reliable Connection)可靠连接模式,每个RdmaEndpoint拥有独立的 QP。在建立 RDMA 连接之前,双方必须通过一次 TCP 连接交换 GID、QP 号(QPN)等信息,这个流程称为握手(handshake):

  • 握手以 brpc 的 AppConnect 方式完成,即RdmaConnect(见 rdma_endpoint.h);
  • 握手期间 TCP fd 仍然有效,握手报文是固定结构的HelloMessage,包含 magic 字符串("RDMA")、消息长度、hello/impl 版本、接收块大小、SQ/RQ 大小、LID、GID、QP 号等字段(字段布局注释与序列化实现见 rdma_endpoint.cpp);
  • 握手结束后 TCP 连接保持在 EST 状态,但不再承载业务数据;一旦该 TCP 连接被关闭,对应的 RDMA 连接会被置为错误状态;
  • 握手期间还定义了从C_ALLOC_QPCQ到ESTABLISHED的完整状态机,以及FALLBACK_TCP、FAILED两种异常出口(状态枚举见 rdma_endpoint.h)。例如握手协商失败或网卡不可用时,连接会自动回退到普通 TCP,保证可用性。

2.3 三大传输特性之一:零拷贝(zero copy)

发送零拷贝:所有待发送数据都在 IOBuf 的 Blocks 中,无需 memcpy,直接把各 Block 的地址拼成ibv_sge数组 post 到发送队列。这些 Block 的引用保存在RdmaEndpoint::_sbuf中,必须等对端完成接收后才能释放。

接收零拷贝:接收侧提前把 IOBuf 的 Blocks 作为 receive buffer post 到接收队列,引用保存在RdmaEndpoint::_rbuf中(逻辑见 rdma_endpoint.cpp)。注意:接收侧 post 的每个 Block 有固定大小recv_block_size,因此发送方单条消息不能超过该值,否则接收侧无法完整收下。

小消息走拷贝路径:零拷贝并非无条件生效。HandleCompletion中,当收到的wc.byte_len小于rdma_zerocopy_min_size(默认 512 字节)时,会退化为普通拷贝(_socket->_read_buf.append(_rbuf_data[...], wc.byte_len)),避免为极短消息承担块管理开销,见 rdma_endpoint.cpp。

2.4 三大传输特性之二:滑动窗口流控

流控用于避免发送端过快、压垮慢速接收端(内核 TCP 栈也有类似机制)。RdmaEndpoint通过接收侧的显式 ACK 实现窗口机制:

  • 握手阶段计算出本地窗口容量_local_window_capacity = min(本地 SQ 大小, 对端 RQ 大小) - RESERVED_WR_NUM,其中RESERVED_WR_NUM = 3是为纯 ACK 预留的 WR 数量(见 rdma_endpoint.cpp);
  • _window_size是原子计数,发送前检查窗口是否大于 0,post 一个发送 WR 就减一,收到对端 ACK 再恢复;
  • 为降低 ACK 开销,ACK 编号可以搭便车(piggyback)在普通数据消息的 immediate data 中一起发送,即每条发送 WR 都携带IBV_WR_SEND_WITH_IMM与imm_data,接收侧在HandleCompletion中解析wc.imm_data来批量回收_sbuf并更新窗口(见 rdma_endpoint.cpp 与 rdma_endpoint.cpp)。

2.5 三大传输特性之三:事件抑制(event suppression)

每条消息的大小都被限制在recv_block_size(默认 8KB)内,如果每条消息都触发一次完成事件,性能会非常差(甚至不如拥有 GSO/GRO 的 TCP)。因此RdmaEndpoint会依据数据大小、窗口水位与 ACK 情况为每条消息设置 solicited 标志,控制对端是否产生事件。从发送侧逻辑(rdma_endpoint.cpp)可以看到具体决策:

  • 写队列中最后一条消息、或当前窗口内最后一条消息,强制标记 solicited;
  • 未 solicited 消息数超过窗口容量 1/4、或累计 ACK 超过对端窗口 1/4、或未 solicited 字节数超过 1MB 时,也会强制 solicited,保证接收端能被及时唤醒返回 ACK;
  • 对应的,发送完成事件也做了抑制:每发送窗口容量 1/4 的 WR 才设置一次IBV_SEND_SIGNALED(见 rdma_endpoint.cpp),且 SQ 被刻意放大为sq_size * 5 / 4以容纳最多 1/4 窗口的 unsignaled WR(见 rdma_endpoint.cpp)。

2.6 注册内存与 IOBuf 内存池接管

RDMA 要求所有参与传输的内存必须先行注册(memory registration),而注册开销很大,通常用内存池避免频繁注册。brpc 的做法是:把 IOBuf 的内存分配直接接管到 RDMA 内存池中(实现见 src/brpc/rdma/block_pool.cpp),从而在 IOBuf 层面实现彻底零拷贝且无需复制。由于 IOBuf 缓冲区不直接受用户控制,IOBuf 的总内存消耗需要谨慎管理,文档建议应用按自身需求一次性注册足够的内存。

如果应用想自行管理内存,可以通过IOBuf::append_user_data_with_meta发送自建数据:先调用rdma::RegisterMemoryForRdma(声明见 src/brpc/rdma/rdma_helper.h)注册内存,该函数返回注册内存的 lkey,调用append_user_data_with_meta时需要把 lkey 作为 data meta 一并提供。发送侧在cut_into_sglist_and_iobuf中正是通过读取 block 的 meta 来获取 lkey 的;若 lkey 为 0 且无法从块池解析,会直接报错ERDMAMEM并提示内存未注册(见 rdma_endpoint.cpp)。

2.7 网卡参数的自动探测与默认选择

RDMA 与硬件强相关,涉及 device、port、GID、LID、MaxSge 等概念。brpc 在初始化时从网卡读取这些参数并做出默认选择(逻辑见 src/brpc/rdma/rdma_helper.cpp)。例如 GID 选择逻辑(rdma_helper.cpp):对 InfiniBand 每个端口只有一个 GID,对 RoCE 则有 2 个基于 MAC 的 GID 和 2 个基于 IP 的 GID,一般最后一个 GID 是 IP 生成的 RoCEv2 类型 GID,因此默认取最大 GID 索引。当默认选择不符合预期时,可以按下一节介绍的 flag 方式覆盖。

三、可配置参数全解

RDMA 模块的参数全部通过 gflags 定义,可在启动时以-flag=value的方式覆盖,也可以在 brpc 运行期通过在线 flag 机制动态调整。下表完整列出 docs/en/rdma.md 中的可配置参数,并补充源码中的定义位置(见 rdma_endpoint.cpp、rdma_helper.cpp、block_pool.cpp):

参数含义默认值
rdma_trace_verbose是否在日志中打印 RDMA 连接信息false
rdma_recv_zerocopy是否启用接收侧零拷贝true
rdma_zerocopy_min_size接收侧零拷贝的最小消息大小(字节),小于该值的消息走拷贝路径512
rdma_recv_block_type接收所用块类型:default(8KB)/large(64KB)/huge(2MB)default
rdma_prepared_qp_size应用启动时预创建 QP 的 SQ/RQ 大小128
rdma_prepared_qp_cnt应用启动时预创建的 QP 数量1024
rdma_max_sgesglist 最大长度,0 表示使用设备允许的最大值0
rdma_sq_sizeSQ 大小128
rdma_rq_sizeRQ 大小128
rdma_cqe_poll_once单次从 CQ 轮询的 CQE 数量32
rdma_gid_index使用的本地 GID 表索引,-1 表示最大 GID 索引-1
rdma_port使用的端口号1
rdma_deviceIB 设备名,空表示第一个活跃设备空
rdma_memory_pool_initial_size_mbRDMA 内存池初始 region 大小(MB)1024
rdma_memory_pool_increase_size_mbRDMA 内存池每次增长的 region 大小(MB)1024
rdma_memory_pool_max_regionsRDMA 内存池最大 region 数量4(上限为 16)
rdma_memory_pool_buckets内存池用于降低互斥竞争的 bucket 数4
rdma_memory_pool_tls_cache_num内存池线程本地缓存块数量128

3.1 接收块类型与 IOBuf 块头

rdma_recv_block_type与 IOBuf 的块实现耦合:GlobalInitialize会根据块类型从GetBlockSize取块大小再减去IOBuf_BLOCK_HEADER_LEN(32 字节)得到g_rdma_recv_block_size(见 rdma_endpoint.cpp)。三种块大小定义在 block_pool.cpp:8KB、64KB、2MB,实际可承载载荷分别是 8192-32、65536-32、2MB-32 字节。这解释了为什么「每条消息都被限制在 recv_block_size」——它是减掉 IOBuf 块头后的净载荷。

3.2 QP/SQ/RQ 的边界约束

rdma_sq_size与rdma_rq_size虽然默认是 128,但并非任意取值:RdmaEndpoint构造函数会把小于 16(MIN_QP_SIZE)的值抬升到 16,大于 4096(MAX_QP_SIZE)的值压回 4096(见 rdma_endpoint.cpp)。同时AllocateResources会优先从预创建的资源链表(由rdma_prepared_qp_size、rdma_prepared_qp_cnt控制)里领取 QP/CQ,不足时再现场创建(见 rdma_endpoint.cpp),连接关闭时满足条件的小 QP 会被重置后归还链表复用(见 rdma_endpoint.cpp)。

3.3 内存池的取值边界

内存池参数同样有硬边界:rdma_memory_pool_max_regions合法范围为 [1, 16](源码RDMA_MEMORY_POOL_MAX_REGIONS = 16,注意默认 flag 值实际为 4,文档中标注的 16 是允许的上限);初始/增长 region 大小范围为 [32, 1048576] MB(见 block_pool.cpp)。region 大小会按块大小与 bucket 数做正则化对齐,超出上限时会输出 "Memory pool reaches max regions" 日志并返回ENOMEM(见 block_pool.cpp)。

四、连接状态与调试手段

RdmaEndpoint内部维护了一套清晰的握手状态机(GetStateStr,见 rdma_endpoint.cpp),包括 UNINIT、C_ALLOC_QPCQ、C_HELLO_SEND、C_HELLO_WAIT、C_BRINGUP_QP、C_ACK_SEND、S_HELLO_WAIT、S_ALLOC_QPCQ、S_BRINGUP_QP、S_HELLO_SEND、S_ACK_WAIT、ESTABLISHED、FALLBACK_TCP、FAILED。把-rdma_trace_verbose=true打开后,可以在日志中看到 "Start handshake on ..."、"Handshake ends (use rdma) on ..." 等连接过程信息。

此外,RdmaEndpoint::DebugInfo(见 rdma_endpoint.cpp)会输出窗口大小、本地/远端窗口容量、sbuf/rbuf 游标、未 ACK 的接收 WR 数、未 solicited 发送数、未 signaled 发送 WR 数等内部状态,配合 brpc 的调试接口可以快速定位窗口耗尽、事件抑制过度等问题。

五、小结与使用建议

综合以上内容,在 brpc 中启用 RDMA 的完整链路是:以任一构建方式带上--with-rdma/-DWITH_RDMA=ON/--define=BRPC_WITH_RDMA=true编译 → 在ChannelOptions/ServerOptions中把use_rdma置为 true → 通过握手完成 TCP 换 RDMA 的升级,数据随之走 verbs 通道。过程中需要注意:

  • 单条消息不能超过接收块净载荷(默认 8KB 减去 32 字节),需要更大消息请切换rdma_recv_block_type=large或huge;
  • IOBuf 内存已被块池接管,务必按业务峰值评估rdma_memory_pool_*参数,避免 OOM 或频繁扩 region;
  • 使用自管内存时,必须正确调用rdma::RegisterMemoryForRdma并让 lkey 随append_user_data_with_meta传递,否则会触发ERDMAMEM错误;
  • 硬件环境(设备、端口、GID 索引)不匹配时,通过rdma_device、rdma_port、rdma_gid_index等 flag 显式指定即可。

更深入的理解可以继续阅读 src/brpc/rdma/rdma_endpoint.cpp 中的发送(CutFromIOBufList)、接收(PollCq/HandleCompletion)与 QP 状态机(BringUpQp,RESET→INIT→RTR→RTS 三段式迁移,见 rdma_endpoint.cpp),以及 src/brpc/rdma/block_pool.cpp 的块池实现。

【免费下载链接】brpc

brpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".

项目地址:https://gitcode.com/gh_mirrors/brpc3/brpc
点击查看免费下载
上一篇:Apache Ignite Java快速入门指南
下一篇:Boost.Beast中的WebSocket控制帧详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询