brpc/docs/cn/rdma.md
2022-12-14 20:13:26 +08:00

6.1 KiB
Raw Permalink Blame History

编译

由于RDMA对驱动与硬件有要求目前仅支持在Linux系统编译并运行RDMA功能。

使用config_brpc

sh config_brpc.sh --with-rdma --headers="/usr/include" --libs="/usr/lib64 /usr/bin"
make

cd example/rdma_performance  # 示例程序
make

使用cmake

mkdir bld && cd bld && cmake -DWITH_RDMA=ON ..
make

cd example/rdma_performance  # 示例程序
mkdir bld && cd bld && cmake ..
make

使用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

基本实现

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需要等到对端确认接收完成后才可以释放这些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_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: 最大的内存池块数默认16
  • rdma_memory_pool_buckets: 内存池中为避免竞争采用的bucket数目默认为4
  • rdma_memory_pool_tls_cache_num: 内存池中thread local的缓存block数目默认为128