Files
2026-07-13 13:29:29 +08:00

91 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 编译
由于RDMA对驱动与硬件有要求,目前仅支持在Linux系统编译并运行RDMA功能。
使用config_brpc
```bash
sh config_brpc.sh --with-rdma --headers="/usr/include" --libs="/usr/lib64 /usr/bin"
make
cd example/rdma_performance # 示例程序
make
```
使用cmake
```bash
mkdir bld && cd bld && cmake -DWITH_RDMA=ON ..
make
cd example/rdma_performance # 示例程序
mkdir bld && cd bld && cmake ..
make
```
使用bazel:
```bash
# Server
bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_server
# Client
bazel build --define=BRPC_WITH_RDMA=true example:rdma_performance_client
```
# 基本实现
RDMA与TCP不同,不使用socket接口进行通信。但是在实现上仍然复用了brpc中原本的Socket类。当用户选择ChannelOptions或ServerOptions中的use_rdma为true时,创建出的Socket类中则有对应的RdmaEndpoint(参见src/brpc/rdma/rdma_endpoint.cpp)。当RDMA被使能时,写入Socket的数据会通过RdmaEndpoint提交给RDMA QP(通过verbs API),而非拷贝到fd。对于数据读取,RdmaEndpoint中则调用verbs API从RDMA CQ中获取对应完成信息(事件获取有独立的fd,复用EventDispatcher,处理函数采用RdmaEndpoint::PollCq),最后复用InputMessenger完成RPC消息解析。
brpc内部使用RDMA RC模式,每个RdmaEndpoint对应一个QP。RDMA连接建立依赖于前置TCP建连,TCP建连后双方交换必要参数,如GID、QPN等,再发起RDMA连接并实现数据传输。这个过程我们称为握手(参见RdmaEndpoint)。因为握手需要TCP连接,因此RdmaEndpoint所在的Socket类中,原本的TCP fd仍然有效。握手过程采用了brpc中已有的AppConnect逻辑。注意,握手用的TCP连接在后续数据传输阶段并不会收发数据,但仍保持为EST状态。一旦TCP连接中断,其上对应的RDMA连接同样会置错。
RdmaEndpoint数据传输逻辑的第一个重要特性是零拷贝。要发送的所有数据默认都存放在IOBuf的Block中,因此所发送的Block需要等到发送CQE触发后才可以释放,这些Block的引用被存放于RdmaEndpoint::_sbuf中。而要实现接收零拷贝,则需要确保接受端所预提交的接收缓冲区必须直接在IOBuf的Block里面,被存放于RdmaEndpoint::_rbuf。注意,接收端预提交的每一段Block,有一个固定的大小(recv_block_size)。发送端发送时,一个请求最多只能有这么大,否则接收端则无法成功接收。
RdmaEndpoint数据传输逻辑的第二个重要特性是滑动窗口流控。这一流控机制是为了避免发送端持续在发送,其速度超过了接收端处理的速度。TCP传输中也有类似的逻辑,但是是由内核协议栈来实现的。RdmaEndpoint内实现了这一流控机制,通过接收端显式回复ACK来确认接收端处理完毕。为了减少ACK本身的开销,让ACK以立即数形式返回,可以被附在数据消息里。
RdmaEndpoint数据传输逻辑的第三个重要特性是事件聚合。每个消息的大小被限定在一个recv_block_size,默认为8KB。如果每个消息都触发事件进行处理,会导致性能退化严重,甚至不如TCP传输(TCP拥有GSO、GRO等诸多优化)。因此,RdmaEndpoint综合考虑数据大小、窗口与ACK的情况,对每个发送消息选择性设置solicited标志,来控制是否在发送端触发事件通知。
RDMA要求数据收发所使用的内存空间必须被注册(memory register),把对应的页表映射注册给网卡,这一操作非常耗时,所以通常都会使用内存池方案来加速。brpc内部的数据收发都使用IOBuf,为了在兼容IOBuf的情况下实现完全零拷贝,整个IOBuf所使用的内存空间整体由统一内存池接管(参见src/brpc/rdma/block_pool.cpp)。注意,由于IOBuf内存池不由用户直接控制,因此实际使用中需要注意IOBuf所消耗的总内存,建议根据实际业务需求,一次性注册足够的内存池以实现性能最大化。
应用程序可以自己管理内存,然后通过IOBuf::append_user_data_with_meta把数据发送出去。在这种情况下,应用程序应该自己使用rdma::RegisterMemoryForRdma注册内存(参见src/brpc/rdma/rdma_helper.h)。注意,RegisterMemoryForRdma会返回注册内存对应的lkey,请在append_user_data_with_meta时以meta形式提供给brpc。
RDMA是硬件相关的通信技术,有很多独特的概念,比如device、port、GID、LID、MaxSge等。这些参数在初始化时会从对应的网卡中读取出来,并且做出默认的选择(参见src/brpc/rdma/rdma_helper.cpp)。有时默认的选择并非用户的期望,则可以通过flag参数方式指定。
RDMA支持事件驱动和轮询两种模式,默认是事件驱动模式,通过设置rdma_use_polling可以开启轮询模式。轮询模式下还可以设置轮询器数目(rdma_poller_num),以及是否主动放弃CPUrdma_poller_yield)。轮询模式下还可以设置一个回调函数,在每次轮询时调用,可以配合io_uring/spdk等使用。
`event_dispatcher_edisp_unsched` 是全局开关,同时影响普通模式(TCP)和 RDMA 模式的 EventDispatcher 调度行为。
值为 `true` 时 EventDispatcher 不可被调度,值为 `false` 时保持可调度(默认)。
运行时严格按用户配置值生效。
推荐使用方式:
1. 默认不配置,保持 `false`
2. 需要不可调度行为时,设置 `-event_dispatcher_edisp_unsched=true`
行为示例:
1. `-event_dispatcher_edisp_unsched=false`TCP 和 RDMA 均可调度。
2. `-event_dispatcher_edisp_unsched=true`TCP 和 RDMA 均不可调度。
# 参数
可配置参数说明:
* rdma_trace_verbose: 日志中打印RDMA建连相关信息,默认false。
* rdma_recv_zerocopy: 是否启用接收零拷贝,默认true。
* rdma_zerocopy_min_size: 接收零拷贝最小的msg大小,默认512B。
* rdma_recv_block_type: 为接收数据预准备的block类型,分为三类default(8KB)/large(64KB)/huge(2MB),默认为default。
* rdma_prepared_qp_size: 程序启动预生成的QP的大小,默认128。
* rdma_prepared_qp_cnt: 程序启动预生成的QP的数量,默认1024。
* rdma_max_sge: 允许的最大发送SGList长度,默认为0,即采用硬件所支持的最大长度。
* rdma_sq_size: SQ大小,默认128。
* rdma_rq_size: RQ大小,默认128。
* rdma_cqe_poll_once: 从CQ中一次性poll出的CQE数量,默认32。
* rdma_gid_index: 使用本地GID表中的Index,默认为-1,即选用最大的可用GID Index。
* rdma_port: 使用IB设备的port number,默认为1。
* rdma_device: 使用IB设备的名称,默认为空,即使用第一个active的设备。
* rdma_memory_pool_initial_size_mb: 内存池的初始大小,单位MB,默认1024。
* rdma_memory_pool_increase_size_mb: 内存池每次动态增长的大小,单位MB,默认1024。
* rdma_memory_pool_max_regions: 最大的内存池块数,默认3。
* rdma_memory_pool_buckets: 内存池中为避免竞争采用的bucket数目,默认为4。
* rdma_memory_pool_tls_cache_num: 内存池中thread local的缓存block数目,默认为128。
* rdma_use_polling: 是否使用RDMA的轮询模式,默认false。
* rdma_poller_num: 轮询模式下的poller数目,默认1。
* rdma_poller_yield: 轮询模式下的poller是否主动放弃CPU,默认是false。
* event_dispatcher_edisp_unsched: 全局开关,控制EventDispatcher是否不可被调度(true时不可调度),默认是false。
* rdma_disable_bthread: 禁用bthread,默认是false。