当前位置: 首页>后端>正文

RDMA编程养成记

高性能分布式文件系统FastCFS从今年7月份开始适配RDMA 网络,经过4个多月的预研、开发和测试,通过 ibverbs和rdmacm原生支持RDMA的FastCFS 5.0于11月22日发布。

RDMA编程初学者可以了解一下RDMA概念及相关介绍:《初识RDMA技术——RDMA概念,特点,协议,通信流程》。RDMA网络环境可以租用阿里云的第四代神龙架构服务器,如c8y,g8y等,在ECS租用页面记得要勾选“弹性RDMA接口”。

一、RDMA 快速入门

1. 使用perftest测试工具

CentOS和RHEL等Linux发行版:yum install perftest -y

Ubuntu和Debian:apt install perftest -y

perftest提供的测试程序将server和client合为一体,通过命令行参数区分,分别在两台服务器执行,先在一台机器执行 server,然后再另外一台机器执行 client。

消息收发机制的两个测试命令如下:

? ib_send_bw:测试网络带宽

? ib_send_lat:测试网络延迟

ib_send_bw 和 ib_send_lat 的参数基本一致,以ib_send_bw为例:

? server端: ib_send_bw -R -d mlx5_1 -s 4096 -D 60

? client端: ib_send_bw -R -d mlx5_1 -s 4096 -D 60 172.16.168.100

参数说明:

? -d mlx5_1 指定设备名称,可以通过命令 ibv_devices 或者 ibv_devinfo 获得设备名称;

? -s 4096 为消息大小;

? -D 60 为测试时长,单位为秒;

? -R 或 --rdma_cm,表示使用rdmacm建连;

? 172.16.168.100为 server端 RDMA 网卡的IP地址,修改为对应的 IP 地址即可。

2. 编译和执行 demo 程序

CentOS和RHEL等Linux发行版:yum install rdma-core-devel -y

Ubuntu和Debian:apt install libibverbs-dev librdmacm-dev -y

我们采用ibverbs的消息收发机制来传输数据:包括send和receive操作。在网上找到的RDMA编程demo非常实用:

《RDMA编程之服务端 server demo》,将源码复制下来,保存为 rdma_server_test.c。

《RDMA 编程客户端client demo》,将源码复制下来,保存为 rdma_client_test.c。

或者直接获取我们完善后的代码:

? curl -o rdma_server_test.c http://www.fastken.com/test/rdma_server_test.c

? curl -o rdma_client_test.c http://www.fastken.com/test/rdma_client_test.c

编译命令如下:

? gcc -Wall -g -O3 -o rdma_server_test rdma_server_test.c -lrdmacm -libverbs -lpthread

? gcc -Wall -g -O3 -o rdma_client_test rdma_client_test.c -lrdmacm -libverbs -lpthread

执行测试程序:

? ? server端:./rdma_server_test

? ? client 端:./rdma_client_test? serverip? serverport

? ? 例如:./rdma_client_test? 172.16.168.100? 12345

二、FastCFS如何适配RDMA

为了同时支持socket和RDMA两种通信方式,我们把RDMA相关操作封装在底层库 libfastrdma中,基础库 libfastcommon 和网络库 libserverframe通过dlopen和dlsym来调用libfastrdma提供的API,这样就做到了对libfastrdma的弱依赖。

libfastrdma采用ibverbs和rdmacm提供的API,通过ibv_post_send发送数据,通过 ibv_post_recv接收数据。RDMA的数据收发buffer需要通过ibv_reg_mr注册,对应的API为:struct ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr, size_t length, int access);

我们采用对象池的做法,事先分配好buffer并调用ibv_reg_mr进行注册。第一个参数pd没必要每次调用时动态创建,完全可以全局共享,一个RDMA网络设备只需要分配一个pd。在我们的网络框架中,一个连接的网络收发 buffer 通常只有一个(发送和接收共用一个buffer)。

为了避免内存拷贝(即数据零拷贝),RDMA收发数据直接使用ibv_reg_mr注册的buffer,并且数据发送和接收均为异步模式,这个机制和 socket 相比存在如下两个挑战:

? 1. 发送和接收buffer何时才能被回收利用的问题。如果采用单个buffer,则表现为发送和接收重入问题;

? 2. 发送完成通知(opcode 为IBV_WC_SEND)不保证时序的问题。通过实测,发送完成通知不是实时的,非常慢。比如先调用send,然后调用recv,往往会先收到对方回复的数据,然后才收到发送完成通知。在批量(连续)推送数据场景,如果收到IBV_WC_SEND后再进行下一次发送,性能惨不忍睹。

我们采用一问一答的单向请求模式,简单高效地解决了上述问题。这种模式类似传统的Client/Server同步请求模型,即Client向Server发送请求,然后等待响应,收到server响应后,才能发起下次请求。那么Server要主动向Client发起请求(比如推送数据)该怎么办呢?很简单,Server再建立到Client的一个连接就好(建连动作可以由Client发起),这样就能保证单向请求模式。

三、RDMA编程问题排查

RDMA API的出错信息明显存在营养不良的问题,具体原因基本靠猜。帮我解决问题的一个高手点评:“rdma是我用过的最坑的api”。下面分享四个RDMA编程过程中踩到的坑。

案例1、RDMA相关指针不能跨进程(如父子进程)访问

租用阿里云的 g8y 型号的ECS,在 Alibaba Cloud Linux 3下程序运行正常,而在Ubuntu 18.04和 20.04下,出现下列错误:

ibv_query_port fail, errno: 13, error info: Permission denied

ibv_reg_mr fail, errno: 13, error info: Permission denied

经调试和高手指点才发现是RDMA相关指针(如struct ibv_context *、struct ibv_pd * 等)不能跨进程访问(即在父进程中创建或获取,在子进程中使用)。我们的RDMA初始化代码在daemon_init(这个函数会fork两次成为daemon进程)之前,因此出现上述报错;放到daemon_init之后就没问题了。

案例2、对端没有先调用 ibv_post_recv

ibv_post_send 报错, errno: 12, error info: Cannot allocate memory

案例3、client连接超时,server端报错:

rdma_create_qp fail, errno: 22, error info: Invalid argument

现象:在服务重启的时候有这个报错,后面就正常了。

原因:client 2秒连接超时后关闭连接(释放了rdma id),然后server却收到建连请求了。

吐槽一下,错误码好歹是 EPIPE(32),也比 EINVAL(22)强太多了。

案例4、笔误导致的低级错误

获得 wc 后,其status为 21,通过 ibv_wc_status_str(wc.status) 获得的错误信息:general error

问题代码:buffer->mr = ibv_reg_mr(pd, &buffer->buff, buffer->size,

IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);

错误原因:第二参数buffer->buff是指针,不应该再用 & 取其地址(抄代码的锅)。

总结

RDMA API 比较简洁,其编程入门容易,完全掌握和排查问题较难。希望这篇文章对大家有所帮助,RDMA和FastCFS相关问题可以加群或者私信交流。


https://www.xamrdz.com/backend/3t91941361.html

相关文章: