王恒宇,中科院软件所根底软件实验室研究生。次要钻研方向为软硬件交融,对物联网、操作系统、Serverless等方向感兴趣。DatenLord社区async-rdma我的项目贡献者之一,曾获嵌入式芯片与零碎设计比赛一等奖等多项国家级奖项,参加编写《openEuler操作系统》一书。
async-rdma是由DatenLord社区发动的Rust异步RDMA编程库,致力于晋升高性能网络应用的开发效率。在介绍async-rdma之前咱们先来理解一下RDMA的概念及其应用形式。
RDMA简介
RDMA(Remote Direct Memory Access)直译为近程间接内存拜访,是一种将一台主机内存中的内容间接传输到另一台主机内存中的技术。计算机中罕用的DMA技术防止了CPU忙于将数据从I/O设施拷贝到内存,RDMA则将这一能力拓展到了通过网络连接的多台主机之间。
RDMA在初始化阶段实现后能够由用户态程序通过用户态驱动间接操作RDMA网卡收发数据,无需陷入内核,防止了上下文切换,也无需CPU进行内存拷贝。凭借绕过内核(kernel bypass)和零拷贝(zero copy)等个性,RDMA能以极低的CPU占用率实现高吞吐、低时延网络通信。这里简要介绍下文中用到的RDMA的外围概念,若想进一步理解RDMA的细节能够关注知乎的系列博客,或更进一步翻阅RDMA协定标准理解细节。
- MR(Memory Region):RDMA应用程序向操作系统申请和注册的一片内存,用于后续RDMA操作。MR不光有地址和长度,还蕴含其余RDMA特有的元数据,如权限位,除本地读写权限还有远端读写权限等。
- 两种操作类型:
1、单端操作(SEND & RECV)
- 发送、接收数据,一端Send前须要对端先Receive
- 对端CPU须要感知每次收发操作
- 用于替换元数据等小数据量操作
2、双端操作(READ & WRITE)
- 间接对指标主机内存进行读写,申请发送前后对端无需进行操作
- 对端CPU不感知单端操作,读写实现后网卡主动响应
- RDMA的次要劣势所在,实用于大数据量传输
现有RDMA开发方法
咱们应用Socket API编写基于TCP/IP的网络应用,基于RDMA的网络应用开发则应用由C语言编写的Verbs API。Verbs API提供了对立的编程接口,屏蔽了RDMA底层协定简单多样的实现,保障了应用程序的通用性。以下是两个Verbs API实例,暂且不关注其各个参数的具体含意,只察看其类型就能够发现其中隐含的内存平安问题。咱们无奈确定裸指针所指数据是否无效,在开发过程中很容易遇到空指针、野指针、内存泄露和屡次开释等常见内存平安问题。
// 注册MR,应用前需先申请内存struct ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr, size_t length, enum ibv_access_flags access)// 数据申请发送接口int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr)
Rust提供了以上内存平安问题的高效解决方案,从语言层面帮忙咱们防止了上述问题。DatenLord社区提供了Verbs API的Rust封装库:rdma-sys。
为什么要做async-rdma?
然而,Verbs API低层级形象导致的开发效率低的问题是单纯应用Rust封装所无奈解决的。这就是为什么有了rdma-sys库后还要再做一个async-rdma。具体来说,就ibv_post_send()接口而言,参数所用到的数据结构是非常复杂的,间接操作这些参数须要对RDMA有很深刻的理解,这会给开发者带来很大认知累赘,并且应用时极易出错。以下是Verbs简单数据结构的一个例子供大家领会,其成员变量还蕴含其余让人头大的数据结构。
// Verbs简单数据结构示意,无需关注细节struct ibv_send_wr { uint64_t wr_id; struct ibv_send_wr *next; struct ibv_sge *sg_list; int num_sge; enum ibv_wr_opcode opcode; enum ibv_send_flags send_flags; uint32_t imm_data;/* network byte order */ union { struct { uint64_t remote_addr; uint32_t rkey; } rdma; struct { uint64_t remote_addr; uint64_t compare_add; uint64_t swap; uint32_t rkey; } atomic; struct { struct ibv_ah *ah; uint32_t remote_qpn; uint32_t remote_qkey; } ud; } wr; uint32_t xrc_remote_srq_num;};
简化API
每次发送数据申请都要解决这些简单的数据结构显然是十分苦楚的。因而async-rdma的首项工作就是对这些数据结构之间的关系进行整顿,简化操作逻辑。例如库中的Rdma数据结构记录了双端建设稳固连贯所需的各种参数,后续通信过程中用到相干数据可间接复用而无需用户填充。通过高层次形象的数据结构封装,使得每次数据操作只须要用户填入必要的信息。
如下代码所示,申请注册MR时用户只须要指定寄存的数据类型或大小即可。SEND操作也只须要指定要发送哪个MR的数据,底层Verbs接口所用到的简单参数由库填充。当然,为了实现操作的便捷性,库中除了提供类型间转换的逻辑外,还应用了一些默认参数。这些对于这些参数将提供批改接口供进阶开发者配置。
// 在本地申请一片寄存i32类型数据的用于RDMA操作的内存空间。let mut lmr = rdma.alloc_local_mr(Layout::new::<i32>())?;// 将上述申请的内存区域中的数据发送到对端。rdma.send(&lmr).await?;
辅助资源管理
只将这些参数的处理过程进行封装也还是不够的,因为这解决不了资源管理艰难的问题。简单的数据结构背地是各种须要治理的资源,如本地注册的内存和从远端申请的内存,以及它们的状态、权限等。
要达到在远端主机CPU不感知的状况下对其内存进行读写,须要双端当时和预先替换元数据用以申请和开释远端资源。即不光要治理本地资源,还要与远端主机协同治理远端资源,相比传统的网络应用开发复杂性更高。async-rdma提供了Verbs API之外面向应用层语义的下层接口,使得建设连贯、远端资源协同治理等操作变得简略。
在建设连贯时,后盾会主动运行Agent服务线程,其接管到下层接口收回的申请后,通过与远端Agent交互进行元数据交换,实现建设连贯、远端资源申请开释等操作。借助于Rust的生命周期机制,当本地或远端资源被Drop时,Agent会清理本地资源或主动向远端发送资源开释的申请,开发者无需感知。以下代码展现了建设连贯、申请本地和远端内存,通过RDMA WRITE操作通信和最终资源开释的过程。
/// 模仿客户端链接服务端async fn client(addr: SocketAddrV4) -> io::Result<()> { // 通过TCP连贯远端,与远端替换用于建设稳固连贯的元数据并启动Agent // 连贯建设后,后续元数据交换通过RDMA SEND RECV操作进行 let rdma = Rdma::connect(addr, 1, 1, 512).await?; // 申请一块本地用于RDMA操作的内存(MR) let mut lmr = rdma.alloc_local_mr(Layout::new::<i32>())?; // 申请一块远端的内存(MR) let mut rmr = rdma.request_remote_mr(Layout::new::<i32>()).await?; // 将要发送的数据填入本地MR unsafe { *(lmr.as_ptr() as *mut i32) = 666 }; // 通过RDMA WRITE操作将本地内存所存数据写入远端内存,远端不感知也无需解决本次申请 rdma.write(&lmr, &mut rmr).await?; // rmr会在生命周期完结时向远端发送开释申请, lmr开释逻辑只需在本地执行。 Ok(())}
异步I/O
如本我的项目名中的async,本库提供的波及I/O的接口都是异步的。应用Verbs API时,每一次RDMA SEND、WRITE等申请后,都须要应用poll()办法轮询接管对端应答,能力确定申请是否正确实现。首先忙等必定是低效的,当然也能够由开发者实现异步逻辑,但轮询到的响应是后面哪次申请的也不确定,须要额定的逻辑去判断。
为了防止所有开发者都实现一遍上述逻辑,咱们借助tokio实现了异步的RDMA申请解决逻辑。以后task收回RDMA申请后即让出,当对端响应事件返回本机后,由后盾服务程序唤醒与响应信息对应的task。这防止了忙等开销,也无需开发者解决申请与响应间的对应关系,具体实现可见仓库中的文档。当然,事件驱动的异步逻辑在进步机器利用效率的同时可能会带来肯定时延。对低时延有极致要求的场景能够就义效率换取低时延,有相干需要的敌人能够参加奉献忙等接口。
结语
总结一下,async-rdma次要提供了以下接口和性能:
- 用于主机之间建设RDMA稳固连贯的异步接口,如connect()。
- 高形象层级的异步RDMA数据收发操作接口,如write()。
- 高形象层级的异步远端RDMA内存治理接口和本地RDMA内存治理接口,如alloc_remote_mr()。
- 用于反对上述异步接口,以及辅助治理远端内存的后盾服务,如Agent。
除此之外,async-rdma还在不断完善谬误的主动解决和提供尽可能具体的谬误提示信息。Verbs API在进行MR的治理或数据传输操作时遇到谬误只返回零碎错误代码,蕴含的信息很少。有的错误处理逻辑会呈现下层错误代码笼罩底层错误代码,返回对开发者具备误导性的错误代码。一些谬误在本人的应用程序中很难发现,要到用户态驱动甚至内核代码中去找。这显然是极为低效和不敌对的,也是async-rdma想要解决的问题。
以后async-rdma尚处于晚期开发阶段,提供了罕用性能接口,但我的项目在稳定性等方面仍有待增强。DatenLord的指标是将其打造成产品级软件,使之可能广泛应用于生产环境中。
咱们近期的次要开发指标如下:
- 进步内存管理效率,革新通用内存分配器jemalloc使其可能调配MR,缩小MR注册等慢速门路操作数量。
- 欠缺错误处理,使后盾服务尝试主动处理错误,并在尝试失败后返回具体错误信息和操作倡议。
- 欠缺测试,包含单元测试,压力测试,性能测试等,以发现潜在问题和辅助性能调优。
- 提供更多配置接口,以后一些配置参数为默认值且尚未给出用户自定义接口,不足灵活性。
其中指标3、4不须要对RDMA或者本我的项目有十分深刻的理解,只需理解对外裸露的下层接口,或相熟Rust语言即可。因而非常适合想要入门的敌人参加奉献,社区也会提供疏导和帮忙。咱们为每一个接口都提供了阐明文档和应用样例,同时提供了在虚拟机中搭建RDMA利用运行环境的配置指南(无需非凡硬件反对),此前未接触过RDMA的敌人也能够尝试。欢送大家提出问题和倡议,欢送对本我的项目感兴趣的敌人退出DatenLord社区一起参加奉献。
DatenLord社区的主我的项目为高性能分布式存储系统:datenlord,async-rdma是其子我的项目之一,更多我的项目见社区github仓库。
相干链接
- async-rdma
- Github:https://github.com/datenlord/...
- Crate:https://crates.io/crates/asyn...
- Docs:https://docs.rs/async-rdma/0....
- rdma-sys:https://github.com/datenlord/...
- DatenLord:https://github.com/datenlord
- RDMA专栏文章: https://zhuanlan.zhihu.com/p/...
- RDMA协定标准:https://cw.infinibandta.org/d...