RDMA 在典型场景下的技术应用分析与探索

RDMA 传输的适配,从业务场景的使用角度来看,大致可分为如下几种类型。

   场景一:机器学习、分布式存储等场景,使用社区成熟的方案,如在机器学习场景中使用的 NCCL、Tensorflow 等框架中都适配了多种传输方式(包含 tcp、rdma 等),块存储 Ceph 中也同时支持 tcp 及 rdma 两种通信模式,这种业务场景下业务侧更多关注的是配置及使用,在 IAAS 基础设施侧将 RDMA 环境准备好后,使能框架使用 rdma 的传输模式即可。

   场景二:业务程序使用类似于 RPC 远程调用的通信方式,业务侧需要将原有使用的 RPC(大部分是 GRPC)调用改为 ORPC 调用,在这种场景下业务和传输更像是两个独立的模块,通过 SDK 的方式进行调用,所以适配起来改造的代码并不多,通常是业务层面修改调用 RPC 的接口方式。但由于业务方可能使用多种编程语言,RPC over RDMA 需要进行编程语言进行适配。

   场景三:业务程序通信是私有化通信,比如使用 socket 套接字结合 epoll 完全自有实现的一套通信机制。这种场景下其实改造也区分情况,即业务 IO 与网络 IO 是否耦合,若比较解耦,代码中抽象出一层类似于最新 Redis 代码中 ConnectionType 这样的架构 [2],那么只需要实现一套基于 RDMA 通信且符合 Redis ConnectionType 接口定义的新传输类型即可,改造量相对可控并且架构上也比较稳定;而若业务 IO 与网络 IO 结合的较为紧密的情况下,这种场景下往往改造起来会比较复杂,改造的时候需要抽丝剥茧的找出业务与网络之间的边界,再进行网络部分的改造。

02

       Redis RDMA 改造方案分析   

    首先,以 Redis 改造为 RDMA 传输为例,分析基于 RDMA 传输的应用程序改造逻辑与流程。

   第一步是需要梳理出来 Redis 中与网络传输相关的逻辑,这部分有比较多的参考资料,这里简单总结一下。

     Redis 中实现了一套 Reactor 模式的事件处理逻辑名为 AE,其主要流程为:

1、使用 epoll 等机制监听各文件句柄,包括新建连接、以及已建立的连接等;

2、根据事件的不同调用对应的事件回调处理;

3、循环进行 epoll loop 并进行处理。

   参考 [2] 中分析了当前 redis 的连接管理是围绕 connection 这个对象进行管理(可类比 socket 套接字的管理),抽象一层高于 socket 的 connection layer,以便兼容不同的传输层,各个字段解释如下。

type:各种连接类型的回调接口,定义了诸如事件回调、listen、accept、read、write 等接口,类比 tcp socket 实现的 proto_ops。

state:当前连接的状态,如 CONNECTING/ACCEPTING/CONNECTED/CLOSED 等状态,类比 TCP 的状态管理。

fd:连接对应的文件句柄。

iovcnt:进行 iov 操作的最大值。

private_data:保存私有数据,当前存放的是 redis 中 client 的指针。

conn_handler/write_handler/read_handler:分别对应连接 connect、write、read 时的处理接口。

get_type: connection 的连接类型,当前 redis 已支持 tcp、unix、tls 类型,返回字符串。

init:在每种网络连接模块注册时调用,各模块私有初始化,如 tcp、unix 类型当前未实现,tls 注册时做了一些 ssl 初始化的前置工作。

ae_handler: redis 中的网络事件处理回调函数,redis 中使用 aeCreateFileEvent 为某个 fd 及事件注册处理函数为 ae_handler,当 redis 的主循环 aeMain 中发现有响应的事件时会调用 ae_handler 进行处理,如在 tcp 连接类型中 ae_handler 为 connSocketEventHandler,该函数分别处理了链接建立、链接可读、链接可写三种事件。

listen: 监听于某个 IP 地址和端口,在 tcp 连接类型中对应的函数为 connSocketListen,该函数主要调用 bind、listen。

accept_handler: redis 作为一个服务端,当接收到客户端新建连接的请求时候的处理函数,一般会被.accept 函数调用惯导模块在无人驾驶中的应用有哪些?,比如在 tcp 连接类型中,connSocketAccept 调用 accept_handler,该方法被注册为 connSocketAcceptHandler,主要是使用 accept 函数接收客户端请求,并调用 acceptCommonHandler 创建 client。

addr: 返回连接的地址信息,主要用于一些连接信息的 debug 日志。

is_local:返回连接是否为本地连接,redis 在 protected 模式下时,调用该接口判断是否为本地连接进行校验。

conn_create/conn_create_accepted:创建 connection,对于 tcp 连接类型,主要是申请 connection 的内存,以及 connection 初始化工作。

shutdown/close:释放 connection 的资源,关闭连接,当某个 redis 客户端移除时调用。

connect/blocking_connect:实现 connection 的非阻塞和阻塞连接方法,在 tcp 连接类型中,非阻塞连接调用 aeCreateFileEvent 注册连接的可写事件,继而由后续的 ae_handler 进行处理,实现非阻塞的连接;而阻塞连接则在实现时会等待连接建立完成。

accept:该方法在 redis 源码中有明确的定义,可直接调用上述 accept_handler,tcp 连接类型中,该方法被注册为 connScoketAccept。

write/writev/read:和 linux 下系统调用 write、writev、read 行为一致,将数据发送至 connection 中,或者从 connection 中读取数据至相应缓冲区。

set_write_handler:注册一个写处理函数,tcp 连接类型中,该方法会注册 connection 可写事件,回调函数为 tcp 的 ae_handler。

set_read_handler:注册一个读处理函数,tcp 连接类型中,该方法会注册 connection 可读事件,回调函数为 tcp 的 ae_handler。

sync_write/sync_read/sync_readline:同步读写接口,在 tcp 连接类型中实现逻辑是使用循环读写。

has_pending_data:检查 connection 中是否有尚未处理的数据,tcp 连接类型中该方法未实现,tls 连接类型中该方法被注册为 tlsHasPendingData,tls 在处理 connection 读事件时,会调用 SSL_read 读取数据,但无法保证数据已经读取完成 [3],所以在 tlsHasPendingData 函数中使用 SSL_pending 检查缓冲区是否有未处理数据,若有的话则交由下面的 process_pending_data 进行处理。has_pending_data 方法主要在事件主循环 beforesleep 中调用,当有 pending data 时,事件主循环时不进行 wait,以便快速进行下一次的循环处理。

process_pending_data:处理检查 connection 中是否有尚未处理的数据,tcp 连接类型中该方法未实现,tls 连接类型中该方法被注册为 tlsProcessPendingData,主要是对 ssl 缓冲区里面的数据进行读取。process_pending_data 方法主要在事件主循环 beforesleep 中调用。

get_peer_cert:TLS 连接特殊方法。

结合当前代码中 tcp 及 tls 实现方法,梳理出和 redis connection 网络传输相关的流程:

图:Redis Connection Call Graph

   对于 redis 来说新增一个 RDMA 方式的传输方式,即是要将 connection 中的各种方法按照上述定义去使用 RDMA 编程接口去实现。RDMA 编程一般采用 CM 管理连接加 Verbs 数据收发的模式,客户端与服务端的交互逻辑大致如下图所示,参考 [16]。

图:RDMA C/S Workflow

    字节跳动的 pizhenwei 同学目前在 redis 社区中已经提交了 redis over rdma 的 PR,参见 [4],具体的代码均在 rdma.c 这一个文件中。由于 RDMA 在做远程内存访问时,需要使用对端的内存地址,所以作者实现了一套 RDMA 客户端与服务端的交互机制,用于通告对端进行远程内存写入的内存地址,参见 [5]。

交互逻辑及说明如下:

1、增加了 RedisRdmaCmd,用于 Redis 客户端与服务端的控制面交互,如特性交换、Keepalive、内存地址交换等;

2、在客户端及服务端建立完成 RDMA 连接后,需要先进行控制面的交互,当内存地址交换完成后,方可以进行 Redis 实际数据的交互及处理;

3、控制面消息通过 IBV_WR_SEND 方式发送,Redis 数据交互通过 IBV_WR_RDMA_WRITE_WITH_IMM 发送,通过方法的不同来区分是控制面消息还是 Redis 的实际数据;

4、客户端及服务端共享了一片内存,则需要对内存的使用管理,目前有三个变量用户协同读写双方的内存使用。

  • tx.offset 为 RDMA 发送侧已经对内存写入的偏移地址,从发送端角度看内存已经使用到了 tx.offset 位置,下次发送端再进行 RDMA 写入时,内存地址只能为 tx.offset + 1;

  • rx.offset 为 RDMA 接收侧已经收到的内存偏移地址,虽然数据可能实际上已经到了 tx.offset 的位置,但由于接收侧需要去处理 CQ 的事件,才能获取到当前数据的位置,rx.offset 是通过 IMM 中的立即数进行传递的,发送侧每次写入数据时,会将数据长度,所以 rx.offset <= tx.offset;

  • rx.pos 为接收方上层业务内存的偏移地址,rx.pos <= rx.offset。

5、当 rx.pos 等于 memory.len 时,说明接收侧内存已满,通过内存地址交换这个 RedisRdmaCmd 进行控制面交互,将 tx.offset、rx.offset、rx.pos 同时置零,重新对这片共享内存协同读写。

Connection 各方法的主要实现逻辑及分析如下:

listen:主要涉及 RDMA 编程图示中 listen、bind 的流程,结合 redis 的.init 相关调用流程,会将 cm_channel 中的 fd 返回给网络框架 AE,当后续客户端连接该 fd 时,由 AE 进行事件回调,即后续的 accepHandler。

accept_handler:该函数作为上述 listen fd 的事件回调函数,会处理客户端的连接事件,主要调用.accept 方法进行接收请求惯导模块在航空飞行器中的应用,并使用 acceptCommonHandler 调用后续的.set_read_handler 注册已连接的读事件,参见图 Redis Connection Call Graph。

accept:要涉及 RDMA 编程图示中 accept 的流程,处理 RDMA_CM_EVENT_CONNECT_REQUEST、RDMA_CM_EVENT_ESTABLISHED 等 cm event,并进行 cm event 的 ack。

set_read_handler:设置连接可读事件的回调为.ae_handler。

read_handler:实际处理中会被设置为 readQueryFromClient。

read:从本地缓冲区中读取数据,该数据是客户端通过远程 DMA 能力写入。

set_write_handler:将 write_handler 设置为回调处理函数,这里和 tcp、tls 实现的方式有所区别,并没有注册 connection 的可写事件回调,是因为 RDMA 中不会触发 POLLOUT(可写)事件,connection 的写由 ae_handler 实现。

write_handler:实际工作中被设置为 sendReplyToClient。

write:将 Redis 的数据拷贝到 RMDA 的本地缓冲区中,通过 ibv_post_send,这部分数据会通过远程 DMA 能力写入对端。

has_pending_data:检查内部的 pending_list,在收到 RDMA_CM_EVENT_DISCONNECTED 等事件时,会将当前 connection 加入到 pending_list 中,由后续 beforeSleep 时调用 process_pending_data 进行处理。

process_pending_data:检查 pending 的 connection,并调用 read_handler 读取 connection 中的数据。

ae_handler:该方法有三个处理流程,第一是处理 RDMA CQ 事件,包括接收处理 RedisRdmaCmd 控制面消息,接收 RDMA IMM 类事件增加 rx.offset;第二是调用 read_handler 和 write_handler,这部分是与 tcp、tls 流程一致;第三是检查 rx.pos 和 rx.offset 的值,若 rx.pos == memory.len 时,发送内存地址交换这个 RedisRdmaCmd 控制面消息。

03

   Redis RDMA 测试   

   Redis 测试通常采取自带的 redis-benchmark 工具进行测试,该工具复用了 redis 中的 ae 处理逻辑,并调用 hiredis 进行 redis 数据的解析,在参考 [6] 中 fork 并改造了一份基于 RDMA 的 redis-benchmark,可直接编译使用,接下来使用该工具进行 tcp 及 RDMA 方式的性能测试对比。

   在实际测试中使用的是同一个交换机下的两台服务器,传输方式是 rocev2,经过 qperf 的测试,tcp 的 latency 为 12us,rocev2 的 latency 为 4us。

3.1 单并发单线程

TCP 方式

RedisServer:./src/redis-server --protected-mode no

RedisBenchmark:./src/redis-benchmark -h xx.xx.xx.xx -p 6379 -c 1 -n 500000 -t get

RDMA 方式

RedisServer:./src/redis-server --loadmodule src/redis-rdma.so port=6379  bind=xx.xx.xx.xx --protected-mode no

RedisBenchmark:./src/redis-benchmark -h xx.xx.xx.xx -p 6379 -c 1 -n 500000 -t get --rdma

3.2 多并发多线程

Redisbenchmark 单线程 4 连接:

Redisbenchmark 单线程 8 连接:

Redisbenchmark 单线程 16/32 连接:

注:在我们的测试环境中 16 个连接时,redis-benchmark 已经 100%,再进行增加连接数测试时,qps 也不会再增加。

Redisbenchmark 4 线程 4 连接:

Redisbenchmark 4 线程 16 连接:

Redisbenchmark 4 线程 32/64 连接:

注:在我们的测试环境中 4 线程 32 连接时,redis-server 已经 100%,再进行增加连接数测试时,qps 也不会再增加。

更多的连接和线程:

3.3 测试总结

  • 整体而言,在我们的测试环境下,redis 服务能力 rocev2(rdma)的传输方式相较 tcp,有~50% 到~100% 左右的能力提升。

  • 可以发现,由于 rdma bypass 了内核协议栈,相同物理拓扑下 redis 一次读取时延下降了 16us 左右(见 3.1 单并发测试数据),这里额外做了一个测试,选取了另外一组相隔较远的机器进行测试,发现读取时延仍然缩小的是这个数量级,见下图。

  • rdma 方式建链的时间较长,实际测试中连接数越多,redis-benchmark 真正开始测试的时间越长。

04

   开源程序基于 RDMA 方案   

4.1 Tensorflow RDMA

Tensorflow 是一个广泛使用的深度学习框架,在 Tensorflow 中数据通常表示为 Tensor 张量,Tensor 是一个多为数据,可以在不同的设备之间进行传输,以便进行分布式计算。

   在分布式系统中,Tensorflow 可以通过网络传输将 Tensor 从一个节点传输到另一个节点,从 1.1 版本开始支持 RDMA 传输,以下为其基于 RDMA 传输的主要方案,参考 [7][8]。

  • 在 RDMA 传输通道建立之前gnss,使用基于 tcp 的 grpc 通道传输传递 RDMA 的内存地址、MR key、服务地址等信息

  • 内存拷贝方案:

     a)对于可以 DMA 的 Tensor(包括 CPU 上的内存或者 GPU Direct 的内存),采用直接从源 Tensor 写到目标 Tensor 中的方案,实现内存零拷贝

     b)对于非 DMA 得 Tensor,用 protobuf 序列化后,通过 RDMA 方式写到接收端预先注册的内存中

     c)对于不支持 GPU Direct 的 Tensor,通过 RDMA 方式写到接收端的 CPU 内存,再在接收端通过拷贝的方式到 GPU 中,发送与接收 CPU 之间不存在内存拷贝

  • 内部使用 RdmaBuffer 用于 RDMA 读写的内存单元,RdmaBuffer 有三个派生类,分别是 RdmaAckBuffer、RdmaMessageBuffer 和 RdmaTensorBuffer,RdmaMessageBuffer 负责发送 message ,比如请求一个 tensor 等等。一旦一个 message 被发送,message 的接收方需要通过 RdmaAckBuffer 发送一个 ack 来释放发送方的 message buffer。一个 RdmaAckBuffer 和唯一的 RdmaMessageBuffer 绑定。RdmaTensorBuffer 负责发送 tensor,tensor 的接收方需要返回一个 message 来释放发送方的 buffer

  • 对于一个具体的 recv 和 send 流程如下:

a)接收侧发送 RDMA_MESSAGE_TENSOR_REQUEST 消息,其中包含目的 Tensor 的地址,以用于发送侧进行 RDMA 写入。

b)为避免在每个步骤中发送额外的元数据消息,为每个 Tensor 维护一个本地元数据缓存,仅在更改时才会更新,每个 RDMA_MESSAGE_TENSOR_REQUEST 将包含接收方从本地缓存中获取的元数据。发送方将比较消息中的元数据和 Tensor 的新元数据,如果元数据更改,发送侧发送包含新元数据的 RDMA_MESSAGE_META_DATA_RESPONSE。

c)当接收方收到 RDMA_MESSAGE_META_DATA_RESPONSE 时,将更新本地元数据缓存,重新分配结果 / 代理 Tensor,重新发送 Tensor 请求。为了可追溯性,新的消息具有不同的名称 RDMA_MESSAGE_TENSOR_RE_REQUEST。

d)当发送方收到 RDMA_MESSAGE_TENSOR_RE_REQUEST 时,它将使用消息中指定的请求索引定位相关的 RdmaTensorResponse,并调用其 Resume 方法,该方法将 RDMA 写入之前克隆的 Tensor 的内容,到重新请求中指定的新远程地址。

e)当接收方接收到 RDMA 写入时,它将使用立即值作为请求索引,找到相关的 RdmaTensorRequest,然后调用其 RecvTensorContent 方法,包含可能存在的内存复制、反序列化等工作。

 4.2 Brpc RDMA

百度的 brpc 当前的 RDMA 传输实现中,数据传输是使用 RMDA_SEND_WITH_IMM 进行操作,这就要求接收端在接收数据前要先准备好内存并预先 POST RECV。为了实现高效的内存管理,brpc 内部实现了静态内存池,且在 RDMA 数据传输实现中做了如下几点优化,参考 [9][10]。

  • 数据传输零拷贝,要发送的所有数据默认都存放在 IOBuf 的 Block 中,因此所发送的 Block 需要等到对端确认接收完成后才可以释放,这些 Block 的引用被存放于 RdmaEndpoint::_sbuf 中。而要实现接收零拷贝,则需要确保接受端所预提交的接收缓冲区必须直接在 IOBuf 的 Block 里面,被存放于 RdmaEndpoint::_rbuf。注意,接收端预提交的每一段 Block,有一个固定的大小(recv_block_size)。发送端发送时,一个请求最多只能有这么大,否则接收端则无法成功接收。

  • 数据传输有滑动窗口流控,这一流控机制是为了避免发送端持续在发送,其速度超过了接收端处理的速度。TCP 传输中也有类似的逻辑,但是是由内核协议栈来实现的,brpc 内实现了这一流控机制,通过接收端显式回复 ACK 来确认接收端处理完毕。为了减少 ACK 本身的开销,让 ACK 以立即数形式返回,可以被附在数据消息里。

  • 数据传输逻辑的第三个重要特性是事件聚合。每个消息的大小被限定在一个 recv_block_size,默认为 8KB。如果每个消息都触发事件进行处理,会导致性能退化严重,甚至不如 TCP 传输(TCP 拥有 GSO、GRO 等诸多优化)。因此,brpc 综合考虑数据大小、窗口与 ACK 的情况,对每个发送消息选择性设置 solicited 标志,来控制是否在发送端触发事件通知。

4.3 NCCL RDMA

      NCCL 的网络传输实现是插件式的,各种不同的网络传输只需要按照 ncclNet 中定义的方法去具体实现即可。

      其中最主要的是 isend、irecv 及 test 方法,在调用 isend 或 irecv 之前,NCCL 将在所有缓冲区上调用 regMr 函数,以便 RDMA NIC 准备缓冲区,deregMr 将用于注销缓冲区。

   以下是 NCCL RDMA 的实现部分逻辑,基于当前 NCCL 最新版本 https://github.com/NVIDIA/nccl/tree/v2.18.3-1 分析,主要参考 [11] 及参考 [12]

(当前实现与参考中略有不同)。

  • 在 NCCL 基于 RDMA 的传输实现中,目前的数据传输主要是通过 RDMA_WRITE 操作

  • 由于发送端进行 RDMA_WRITE 时,需要预先知道对端的 DMA 地址,NCCL 中发送 / 接收端是通过一个缓冲区 ncclIbSendFifo 进行交互

  • ncclIbSendFifo 是发送端申请的一块内存缓冲区,在 connect 与 accept 阶段通过传统 tcp socket 的方式携带给接收端

  • 在接收端异步进行接收时,recvProxyProgress 调用 irecv 接口进行接收,在 RDMA 的实现中对应的是将本端 DMA 的地址通过 ncclIbSendFifo RDMA_WRITE 至发送端

  • 发送端进行发送时,sendProxyProgress 调用 isend 接口进行发送,在 RDMA 中对应的是从 ncclIbSendFifo 中获取接收端的 DMA 地址,将上层的 data 直接 RDMA_WRITE 至接收端的 DMA 地址中

  • 接收端维护本地的 remFifoTail 游标,每次接收时游标后移一位,接收端会将 idx 设置为一个自增的索引,同时将上层的 DMA 地址通过 ncclIbSendFifo 携带给发送端

  • 发送端维护本地的 fifoHead 游标,每次发送后游标后移一位,发送端检查 fifo 中元素的 idx 值是否为预期索引来判断该 fifo 是否已经被接收端设置过,即接收端的 DMA 地址已经可以写入

struct ncclIbSendFifo {  uint64_t addr;  int      size;  uint32_t rkey;  uint32_t nreqs;  uint32_t tag;  uint64_t idx;};
// 发送端ncclIbIsend:uint64_t idx = comm->fifoHead+1;if (slots[0].idx != idx) { *request = NULL; return ncclSuccess; }comm->fifoHead++;
//接收端ncclIbIrecv -> ncclIbPostFifo :localElem[i].idx = comm->remFifo.fifoTail+1;comm->remFifo.fifoTail++;

4.4 Libvma 及 SMC-R 方式

除了上述修改业务源码的方案,业内也有 “零入侵” 业务程序的方案,比如 libvma 及 smc-r 方式。

SMC-R:

smc-r(SMC over RDMA)是 IBM 在 2017 提交至 linux kernel 的一种兼容 socket 层,使用共享内存技术、基于 RDMA 技术实现的高性能内核网络协议栈。smc-r 的主要实现是在内核态实现了一个新的 af_smc 协议族,基于 RDMA verbs 接口实现内核 proto_ops 中的各方法。

smc-r 支持 fallback 回退机制,在通信双方最开始建立连接时是使用 tcp 握手(特定的 tcp 选项)进行协商是否双方均支持 SMC-R 能力,当协商不成功时 fallback 为原始的 tcp 通信。完成协议协商并建立连接后,协议栈为 SMC-R socket 分配一块用于缓存待发送数据的环形缓冲区 sndbuf 和一块用于缓存待接收数据的环形缓冲区 RMB(Remote Memory Buffer)。

  • 发送端应用程序通过 socket 接口将待发送数据拷贝到本侧 sndbuf 中,由 SMC-R 协议栈通过 RDMA WRITE 操作直接高效地写入对侧节点的 RMB 中。同时伴随着使用 RDMA SEND/RECV 操作交互连接数据管理消息,用于更新、同步环形缓冲区中的数据游标。

  • 接收端 SMC-R 协议栈感知到 RMB 中填入新数据后,通过 epoll 等方式告知接收端应用程序将 RMB 中的数据拷贝到用户态,完成数据传输。所以在 SMC-R 中,RMB 充当传输过程中的共享内存。

图 smc-r 发送接收 (转自阿里云)

下面是一个基于 smc-r 通信的实际测试场景的协商交互抓包:

Libvma:

Libvma 是 Mellanox 公司开源的一款高性能的用户态网络协议栈,它将 socket 的相关接口全部在用户态空间实现,实现对内核的旁路,使用 RDMA verbs 接口直接调用网卡驱动,从而节省了大量的上下文数据拷贝,节省了 CPU 的资源降低了时延,业务在使用 libvma 时只需要使用 LD_PRELOAD libvma.so 替换原有的系统调用即可完成传输协议的替换。

Libvma 内部在 tcp 协议栈的实现上使用了 lwip 方案,重写了 epoll,使用了 hugepage,内部使用单独的线程去轮询 RDMA CQ 事件等方案,相较于内核协议栈的实现,在主机侧的处理延迟有 200% 至 500% 的降低。

此外,在实际测试过程中发现 libvma 虽然使用的是 RDMA verbs 接口,但实际针对 Mellanox mlx5 系列驱动的网卡是直接用户态驱动网卡,发送的仍然是原始基于 tcp 的以太报文,并不是 rocev2 的报文,具体讨论可以见 github 上的 issue 参考 [15]。

下面是基于 libvma 测试 redis 的场景,由于 libvma bypass 协议栈,并且重写了 epoll 等其它特性,性能提升大概 3 倍:

总结:

相较于业务使用 raw verbs 进行源码修改,libvma 及 smc-r 方式可以提供 “零入侵、零修改” 源码的优势,但由于应用程序在将数据提供给 socket 接口时仍然存在一次拷贝,所以性能上对比 verbs 方案来说有一定的损耗,对于想快速验证 RDMA 能力的业务是一个不错的 POC 验证方式。

目前阿里云的 Alibaba Cloud Linux3 默认支持 smc-r 能力,结合阿里云的 eRDMA 能力网卡,可以使业务进行透明无损的 RDMA 传输替换,减少 cpu 的使用率,降低一定的通信延时。但目前该能力在阿里云上属于公测能力,生产稳定性待验证,参考 [14]。

libvma 方式没有 linux 社区的支持,并且更多的是针对 Mellanox 系列网卡的支持,在工业界使用的场景也不太多,目前在金融的高频交易领域有一些使用尝试。

05

   总结与展望   

前面主要分析和调研了一些开源应用在进行业务适配 RDMA 传输的方案,整体来看 RDMA 改造的方案是分为两部分,分别为通信接口的改造以及 RDMA 内存管理设计。

通信接口改造主要指将 tcp socket 的传输接口修改为 ib verbs 或者 cm 接口,这部分同时涉及到适配现有业务网络事件的处理模型。

由于 RDMA 传输数据时,需要预先将内存注册到 HCA 卡上,所以 RDMA 内存管理会比较复杂,同时也是性能高低与否的关键。

    1)数据传输时申请内存,并进行内存注册,再进行 RDMA 操作。显然这种模式在代码实现上最为简单,但是性能及效率最低,现有方案中很少有在 fast path 中使用这种内存管理方案。

    2)提前注册好一大块内存,在上层业务需要发送数据时,将数据拷贝至 RDMA 注册好的内存。这种模式性能相较第一种有提升,但存在一定的内存拷贝。

    3)使用内存池,业务及 RDMA 的内存使用同一块。性能明显是最优的,但是实现逻辑较复杂,需要管理好内存的申请及释放、某些实现中通信双方也需要做内存使用量的协商。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值