01
業(yè)務(wù)適配RDMA類型
RDMA傳輸?shù)倪m配,從業(yè)務(wù)場景的使用角度來看,大致可分為如下幾種類型。
場景一:機器學(xué)習(xí)、分布式存儲等場景,使用社區(qū)成熟的方案,如在機器學(xué)習(xí)場景中使用的NCCL、Tensorflow等框架中都適配了多種傳輸方式(包含tcp、rdma等),塊存儲Ceph中也同時支持tcp及rdma兩種通信模式,這種業(yè)務(wù)場景下業(yè)務(wù)側(cè)更多關(guān)注的是配置及使用,在IAAS基礎(chǔ)設(shè)施側(cè)將RDMA環(huán)境準備好后,使能框架使用rdma的傳輸模式即可。
場景二:業(yè)務(wù)程序使用類似于RPC遠程調(diào)用的通信方式,業(yè)務(wù)側(cè)需要將原有使用的RPC(大部分是GRPC)調(diào)用改為ORPC調(diào)用,在這種場景下業(yè)務(wù)和傳輸更像是兩個獨立的模塊,通過SDK的方式進行調(diào)用,所以適配起來改造的代碼并不多,通常是業(yè)務(wù)層面修改調(diào)用RPC的接口方式。但由于業(yè)務(wù)方可能使用多種編程語言,RPC over RDMA需要進行編程語言進行適配。
場景三:業(yè)務(wù)程序通信是私有化通信,比如使用socket套接字結(jié)合epoll完全自有實現(xiàn)的一套通信機制。這種場景下其實改造也區(qū)分情況,即業(yè)務(wù)IO與網(wǎng)絡(luò)IO是否耦合,若比較解耦,代碼中抽象出一層類似于最新Redis代碼中ConnectionType這樣的架構(gòu)[2],那么只需要實現(xiàn)一套基于RDMA通信且符合Redis ConnectionType接口定義的新傳輸類型即可,改造量相對可控并且架構(gòu)上也比較穩(wěn)定;而若業(yè)務(wù)IO與網(wǎng)絡(luò)IO結(jié)合的較為緊密的情況下,這種場景下往往改造起來會比較復(fù)雜,改造的時候需要抽絲剝繭的找出業(yè)務(wù)與網(wǎng)絡(luò)之間的邊界,再進行網(wǎng)絡(luò)部分的改造。
02
Redis RDMA改造方案分析
首先,以Redis改造為RDMA傳輸為例,分析基于RDMA傳輸?shù)膽?yīng)用程序改造邏輯與流程。
第一步是需要梳理出來Redis中與網(wǎng)絡(luò)傳輸相關(guān)的邏輯,這部分有比較多的參考資料,這里簡單總結(jié)一下。
Redis中實現(xiàn)了一套Reactor模式的事件處理邏輯名為AE,其主要流程為:
1、使用epoll等機制監(jiān)聽各文件句柄,包括新建連接、以及已建立的連接等;
2、根據(jù)事件的不同調(diào)用對應(yīng)的事件回調(diào)處理;
3、循環(huán)進行epoll loop并進行處理。
參考[2]中分析了當(dāng)前redis的連接管理是圍繞connection這個對象進行管理(可類比socket套接字的管理),抽象一層高于socket的connection layer,以便兼容不同的傳輸層,各個字段解釋如下。
type:各種連接類型的回調(diào)接口,定義了諸如事件回調(diào)、listen、accept、read、write等接口,類比tcp socket實現(xiàn)的proto_ops。
state:當(dāng)前連接的狀態(tài),如CONNECTING/ACCEPTING/CONNECTED/CLOSED等狀態(tài),類比TCP的狀態(tài)管理。
fd:連接對應(yīng)的文件句柄。
iovcnt:進行iov操作的最大值。
private_data:保存私有數(shù)據(jù),當(dāng)前存放的是redis中client的指針。
conn_handler/write_handler/read_handler:分別對應(yīng)連接connect、write、read時的處理接口。
get_type: connection的連接類型,當(dāng)前redis已支持tcp、unix、tls類型,返回字符串。
init:在每種網(wǎng)絡(luò)連接模塊注冊時調(diào)用,各模塊私有初始化,如tcp、unix類型當(dāng)前未實現(xiàn),tls注冊時做了一些ssl初始化的前置工作。
ae_handler: redis中的網(wǎng)絡(luò)事件處理回調(diào)函數(shù),redis中使用aeCreateFileEvent為某個fd及事件注冊處理函數(shù)為ae_handler,當(dāng)redis的主循環(huán)aeMain中發(fā)現(xiàn)有響應(yīng)的事件時會調(diào)用ae_handler進行處理,如在tcp連接類型中ae_handler為connSocketEventHandler,該函數(shù)分別處理了鏈接建立、鏈接可讀、鏈接可寫三種事件。
listen: 監(jiān)聽于某個IP地址和端口,在tcp連接類型中對應(yīng)的函數(shù)為connSocketListen,該函數(shù)主要調(diào)用bind、listen。
accept_handler: redis作為一個服務(wù)端,當(dāng)接收到客戶端新建連接的請求時候的處理函數(shù),一般會被.accept函數(shù)調(diào)用,比如在tcp連接類型中,connSocketAccept調(diào)用accept_handler,該方法被注冊為connSocketAcceptHandler,主要是使用accept函數(shù)接收客戶端請求,并調(diào)用acceptCommonHandler創(chuàng)建client。
addr: 返回連接的地址信息,主要用于一些連接信息的debug日志。
is_local:返回連接是否為本地連接,redis在protected模式下時,調(diào)用該接口判斷是否為本地連接進行校驗。
conn_create/conn_create_accepted:創(chuàng)建connection,對于tcp連接類型,主要是申請connection的內(nèi)存,以及connection初始化工作。
shutdown/close:釋放connection的資源,關(guān)閉連接,當(dāng)某個redis客戶端移除時調(diào)用。
connect/blocking_connect:實現(xiàn)connection的非阻塞和阻塞連接方法,在tcp連接類型中,非阻塞連接調(diào)用aeCreateFileEvent注冊連接的可寫事件,繼而由后續(xù)的ae_handler進行處理,實現(xiàn)非阻塞的連接;而阻塞連接則在實現(xiàn)時會等待連接建立完成。
accept:該方法在redis源碼中有明確的定義,可直接調(diào)用上述accept_handler,tcp連接類型中,該方法被注冊為connScoketAccept。
write/writev/read:和linux下系統(tǒng)調(diào)用write、writev、read行為一致,將數(shù)據(jù)發(fā)送至connection中,或者從connection中讀取數(shù)據(jù)至相應(yīng)緩沖區(qū)。
set_write_handler:注冊一個寫處理函數(shù),tcp連接類型中,該方法會注冊connection可寫事件,回調(diào)函數(shù)為tcp的ae_handler。
set_read_handler:注冊一個讀處理函數(shù),tcp連接類型中,該方法會注冊connection可讀事件,回調(diào)函數(shù)為tcp的ae_handler。
sync_write/sync_read/sync_readline:同步讀寫接口,在tcp連接類型中實現(xiàn)邏輯是使用循環(huán)讀寫。
has_pending_data:檢查connection中是否有尚未處理的數(shù)據(jù),tcp連接類型中該方法未實現(xiàn),tls連接類型中該方法被注冊為tlsHasPendingData,tls在處理connection讀事件時,會調(diào)用SSL_read讀取數(shù)據(jù),但無法保證數(shù)據(jù)已經(jīng)讀取完成[3],所以在tlsHasPendingData函數(shù)中使用SSL_pending檢查緩沖區(qū)是否有未處理數(shù)據(jù),若有的話則交由下面的process_pending_data進行處理。has_pending_data方法主要在事件主循環(huán)beforesleep中調(diào)用,當(dāng)有pending data時,事件主循環(huán)時不進行wait,以便快速進行下一次的循環(huán)處理。
process_pending_data:處理檢查connection中是否有尚未處理的數(shù)據(jù),tcp連接類型中該方法未實現(xiàn),tls連接類型中該方法被注冊為tlsProcessPendingData,主要是對ssl緩沖區(qū)里面的數(shù)據(jù)進行讀取。process_pending_data方法主要在事件主循環(huán)beforesleep中調(diào)用。
get_peer_cert:TLS連接特殊方法。
結(jié)合當(dāng)前代碼中tcp及tls實現(xiàn)方法,梳理出和redis connection網(wǎng)絡(luò)傳輸相關(guān)的流程:
圖:Redis Connection Call Graph
對于redis來說新增一個RDMA方式的傳輸方式,即是要將connection中的各種方法按照上述定義去使用RDMA編程接口去實現(xiàn)。RDMA編程一般采用CM管理連接加Verbs數(shù)據(jù)收發(fā)的模式,客戶端與服務(wù)端的交互邏輯大致如下圖所示,參考[16]。
圖:RDMA C/S Workflow
字節(jié)跳動的pizhenwei同學(xué)目前在redis社區(qū)中已經(jīng)提交了redis over rdma的PR,參見[4],具體的代碼均在rdma.c這一個文件中。由于RDMA在做遠程內(nèi)存訪問時,需要使用對端的內(nèi)存地址,所以作者實現(xiàn)了一套RDMA客戶端與服務(wù)端的交互機制,用于通告對端進行遠程內(nèi)存寫入的內(nèi)存地址,參見[5]。
交互邏輯及說明如下:
1、增加了RedisRdmaCmd,用于Redis客戶端與服務(wù)端的控制面交互,如特性交換、Keepalive、內(nèi)存地址交換等;
2、在客戶端及服務(wù)端建立完成RDMA連接后,需要先進行控制面的交互,當(dāng)內(nèi)存地址交換完成后,方可以進行Redis實際數(shù)據(jù)的交互及處理;
3、控制面消息通過IBV_WR_SEND方式發(fā)送,Redis數(shù)據(jù)交互通過IBV_WR_RDMA_WRITE_WITH_IMM發(fā)送,通過方法的不同來區(qū)分是控制面消息還是Redis的實際數(shù)據(jù);
4、客戶端及服務(wù)端共享了一片內(nèi)存,則需要對內(nèi)存的使用管理,目前有三個變量用戶協(xié)同讀寫雙方的內(nèi)存使用。
tx.offset為RDMA發(fā)送側(cè)已經(jīng)對內(nèi)存寫入的偏移地址,從發(fā)送端角度看內(nèi)存已經(jīng)使用到了tx.offset位置,下次發(fā)送端再進行RDMA寫入時,內(nèi)存地址只能為tx.offset + 1;
rx.offset為RDMA接收側(cè)已經(jīng)收到的內(nèi)存偏移地址,雖然數(shù)據(jù)可能實際上已經(jīng)到了tx.offset的位置,但由于接收側(cè)需要去處理CQ的事件,才能獲取到當(dāng)前數(shù)據(jù)的位置,rx.offset是通過IMM中的立即數(shù)進行傳遞的,發(fā)送側(cè)每次寫入數(shù)據(jù)時,會將數(shù)據(jù)長度,所以rx.offset <= tx.offset;
rx.pos 為接收方上層業(yè)務(wù)內(nèi)存的偏移地址,rx.pos <= rx.offset。
5、當(dāng)rx.pos等于memory.len時,說明接收側(cè)內(nèi)存已滿,通過內(nèi)存地址交換這個RedisRdmaCmd進行控制面交互,將tx.offset、rx.offset、rx.pos同時置零,重新對這片共享內(nèi)存協(xié)同讀寫。
Connection各方法的主要實現(xiàn)邏輯及分析如下:
listen:主要涉及RDMA編程圖示中l(wèi)isten、bind的流程,結(jié)合redis的.init相關(guān)調(diào)用流程,會將cm_channel中的fd返回給網(wǎng)絡(luò)框架AE,當(dāng)后續(xù)客戶端連接該fd時,由AE進行事件回調(diào),即后續(xù)的accepHandler。
accept_handler:該函數(shù)作為上述listen fd的事件回調(diào)函數(shù),會處理客戶端的連接事件,主要調(diào)用.accept方法進行接收請求,并使用acceptCommonHandler調(diào)用后續(xù)的.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:設(shè)置連接可讀事件的回調(diào)為.ae_handler。
read_handler:實際處理中會被設(shè)置為readQueryFromClient。
read:從本地緩沖區(qū)中讀取數(shù)據(jù),該數(shù)據(jù)是客戶端通過遠程DMA能力寫入。
set_write_handler:將write_handler設(shè)置為回調(diào)處理函數(shù),這里和tcp、tls實現(xiàn)的方式有所區(qū)別,并沒有注冊connection的可寫事件回調(diào),是因為RDMA中不會觸發(fā)POLLOUT(可寫)事件,connection的寫由ae_handler實現(xiàn)。
write_handler:實際工作中被設(shè)置為sendReplyToClient。
write:將Redis的數(shù)據(jù)拷貝到RMDA的本地緩沖區(qū)中,通過ibv_post_send,這部分數(shù)據(jù)會通過遠程DMA能力寫入對端。
has_pending_data:檢查內(nèi)部的pending_list,在收到RDMA_CM_EVENT_DISCONNECTED等事件時,會將當(dāng)前connection加入到pending_list中,由后續(xù)beforeSleep時調(diào)用process_pending_data進行處理。
process_pending_data:檢查pending的connection,并調(diào)用read_handler讀取connection中的數(shù)據(jù)。
ae_handler:該方法有三個處理流程,第一是處理RDMA CQ事件,包括接收處理RedisRdmaCmd控制面消息,接收RDMA IMM類事件增加rx.offset;第二是調(diào)用read_handler和write_handler,這部分是與tcp、tls流程一致;第三是檢查rx.pos和rx.offset的值,若rx.pos == memory.len時,發(fā)送內(nèi)存地址交換這個RedisRdmaCmd控制面消息。
03
Redis RDMA測試
Redis測試通常采取自帶的redis-benchmark工具進行測試,該工具復(fù)用了redis中的ae處理邏輯,并調(diào)用hiredis進行redis數(shù)據(jù)的解析,在參考[6]中fork并改造了一份基于RDMA的redis-benchmark,可直接編譯使用,接下來使用該工具進行tcp及RDMA方式的性能測試對比。
在實際測試中使用的是同一個交換機下的兩臺服務(wù)器,傳輸方式是rocev2,經(jīng)過qperf的測試,tcp的latency為12us,rocev2的latency為4us。
▎3.1 單并發(fā)單線程
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多并發(fā)多線程
Redisbenchmark單線程4連接:
Redisbenchmark單線程8連接:
Redisbenchmark單線程16/32連接:
注:在我們的測試環(huán)境中16個連接時,redis-benchmark已經(jīng)100%,再進行增加連接數(shù)測試時,qps也不會再增加。
Redisbenchmark 4線程4連接:
Redisbenchmark 4線程16連接:
Redisbenchmark 4線程32/64連接:
注:在我們的測試環(huán)境中4線程32連接時,redis-server已經(jīng)100%,再進行增加連接數(shù)測試時,qps也不會再增加。
更多的連接和線程:
▎3.3測試總結(jié)
整體而言,在我們的測試環(huán)境下,redis服務(wù)能力rocev2(rdma)的傳輸方式相較tcp,有~50% 到 ~100%左右的能力提升。
可以發(fā)現(xiàn),由于rdma bypass了內(nèi)核協(xié)議棧,相同物理拓撲下redis一次讀取時延下降了16us左右(見3.1單并發(fā)測試數(shù)據(jù)),這里額外做了一個測試,選取了另外一組相隔較遠的機器進行測試,發(fā)現(xiàn)讀取時延仍然縮小的是這個數(shù)量級,見下圖。
rdma方式建鏈的時間較長,實際測試中連接數(shù)越多,redis-benchmark真正開始測試的時間越長。
04
開源程序基于RDMA方案
▎4.1Tensorflow RDMA
Tensorflow是一個廣泛使用的深度學(xué)習(xí)框架,在Tensorflow中數(shù)據(jù)通常表示為Tensor張量,Tensor是一個多為數(shù)據(jù),可以在不同的設(shè)備之間進行傳輸,以便進行分布式計算。
在分布式系統(tǒng)中,Tensorflow可以通過網(wǎng)絡(luò)傳輸將Tensor從一個節(jié)點傳輸?shù)搅硪粋€節(jié)點,從1.1版本開始支持RDMA傳輸,以下為其基于RDMA傳輸?shù)闹饕桨?,參考[7][8]。
在RDMA傳輸通道建立之前,使用基于tcp的grpc通道傳輸傳遞RDMA的內(nèi)存地址、MR key、服務(wù)地址等信息
內(nèi)存拷貝方案:
a)對于可以DMA的Tensor(包括CPU上的內(nèi)存或者GPU Direct的內(nèi)存),采用直接從源Tensor寫到目標Tensor中的方案,實現(xiàn)內(nèi)存零拷貝
b)對于非DMA得Tensor,用protobuf序列化后,通過RDMA方式寫到接收端預(yù)先注冊的內(nèi)存中
c)對于不支持GPU Direct的Tensor,通過RDMA方式寫到接收端的CPU內(nèi)存,再在接收端通過拷貝的方式到GPU中,發(fā)送與接收CPU之間不存在內(nèi)存拷貝
內(nèi)部使用RdmaBuffer用于RDMA讀寫的內(nèi)存單元,RdmaBuffer有三個派生類,分別是RdmaAckBuffer、RdmaMessageBuffer和RdmaTensorBuffer,RdmaMessageBuffer負責(zé)發(fā)送 message ,比如請求一個tensor等等。一旦一個message被發(fā)送,message的接收方需要通過RdmaAckBuffer發(fā)送一個ack來釋放發(fā)送方的message buffer。一個RdmaAckBuffer和唯一的RdmaMessageBuffer綁定。RdmaTensorBuffer負責(zé)發(fā)送tensor,tensor的接收方需要返回一個message來釋放發(fā)送方的buffer
對于一個具體的recv和send流程如下:
a)接收側(cè)發(fā)送RDMA_MESSAGE_TENSOR_REQUEST消息,其中包含目的Tensor的地址,以用于發(fā)送側(cè)進行RDMA寫入。
b)為避免在每個步驟中發(fā)送額外的元數(shù)據(jù)消息,為每個Tensor維護一個本地元數(shù)據(jù)緩存,僅在更改時才會更新,每個RDMA_MESSAGE_TENSOR_REQUEST將包含接收方從本地緩存中獲取的元數(shù)據(jù)。發(fā)送方將比較消息中的元數(shù)據(jù)和Tensor的新元數(shù)據(jù),如果元數(shù)據(jù)更改,發(fā)送側(cè)發(fā)送包含新元數(shù)據(jù)的RDMA_MESSAGE_META_DATA_RESPONSE。
c)當(dāng)接收方收到 RDMA_MESSAGE_META_DATA_RESPONSE 時,將更新本地元數(shù)據(jù)緩存,重新分配結(jié)果/代理Tensor,重新發(fā)送Tensor請求。為了可追溯性,新的消息具有不同的名稱RDMA_MESSAGE_TENSOR_RE_REQUEST。
d)當(dāng)發(fā)送方收到 RDMA_MESSAGE_TENSOR_RE_REQUEST 時,它將使用消息中指定的請求索引定位相關(guān)的 RdmaTensorResponse,并調(diào)用其 Resume方法,該方法將 RDMA 寫入之前克隆的Tensor的內(nèi)容,到重新請求中指定的新遠程地址。
e)當(dāng)接收方接收到 RDMA 寫入時,它將使用立即值作為請求索引,找到相關(guān)的 RdmaTensorRequest,然后調(diào)用其 RecvTensorContent方法,包含可能存在的內(nèi)存復(fù)制、反序列化等工作。
▎4.2BrpcRDMA
百度的brpc當(dāng)前的RDMA傳輸實現(xiàn)中,數(shù)據(jù)傳輸是使用RMDA_SEND_WITH_IMM進行操作,這就要求接收端在接收數(shù)據(jù)前要先準備好內(nèi)存并預(yù)先POST RECV。為了實現(xiàn)高效的內(nèi)存管理,brpc內(nèi)部實現(xiàn)了靜態(tài)內(nèi)存池,且在RDMA數(shù)據(jù)傳輸實現(xiàn)中做了如下幾點優(yōu)化,參考[9][10]。
數(shù)據(jù)傳輸零拷貝,要發(fā)送的所有數(shù)據(jù)默認都存放在IOBuf的Block中,因此所發(fā)送的Block需要等到對端確認接收完成后才可以釋放,這些Block的引用被存放于RdmaEndpoint::_sbuf中。而要實現(xiàn)接收零拷貝,則需要確保接受端所預(yù)提交的接收緩沖區(qū)必須直接在IOBuf的Block里面,被存放于RdmaEndpoint::_rbuf。注意,接收端預(yù)提交的每一段Block,有一個固定的大小(recv_block_size)。發(fā)送端發(fā)送時,一個請求最多只能有這么大,否則接收端則無法成功接收。
數(shù)據(jù)傳輸有滑動窗口流控,這一流控機制是為了避免發(fā)送端持續(xù)在發(fā)送,其速度超過了接收端處理的速度。TCP傳輸中也有類似的邏輯,但是是由內(nèi)核協(xié)議棧來實現(xiàn)的,brpc內(nèi)實現(xiàn)了這一流控機制,通過接收端顯式回復(fù)ACK來確認接收端處理完畢。為了減少ACK本身的開銷,讓ACK以立即數(shù)形式返回,可以被附在數(shù)據(jù)消息里。
數(shù)據(jù)傳輸邏輯的第三個重要特性是事件聚合。每個消息的大小被限定在一個recv_block_size,默認為8KB。如果每個消息都觸發(fā)事件進行處理,會導(dǎo)致性能退化嚴重,甚至不如TCP傳輸(TCP擁有GSO、GRO等諸多優(yōu)化)。因此,brpc綜合考慮數(shù)據(jù)大小、窗口與ACK的情況,對每個發(fā)送消息選擇性設(shè)置solicited標志,來控制是否在發(fā)送端觸發(fā)事件通知。
▎4.3NCCLRDMA
NCCL的網(wǎng)絡(luò)傳輸實現(xiàn)是插件式的,各種不同的網(wǎng)絡(luò)傳輸只需要按照ncclNet中定義的方法去具體實現(xiàn)即可。
其中最主要的是isend、irecv及test方法,在調(diào)用 isend 或 irecv 之前,NCCL 將在所有緩沖區(qū)上調(diào)用 regMr 函數(shù),以便 RDMA NIC 準備緩沖區(qū),deregMr 將用于注銷緩沖區(qū)。
以下是NCCL RDMA的實現(xiàn)部分邏輯,基于當(dāng)前NCCL最新版本分析,主要參考[11]及參考[12]
(當(dāng)前實現(xiàn)與參考中略有不同)。
在NCCL基于RDMA的傳輸實現(xiàn)中,目前的數(shù)據(jù)傳輸主要是通過RDMA_WRITE操作
由于發(fā)送端進行RDMA_WRITE時,需要預(yù)先知道對端的DMA地址,NCCL中發(fā)送/接收端是通過一個緩沖區(qū)ncclIbSendFifo進行交互
ncclIbSendFifo是發(fā)送端申請的一塊內(nèi)存緩沖區(qū),在connect與accept階段通過傳統(tǒng)tcp socket的方式攜帶給接收端
在接收端異步進行接收時,recvProxyProgress調(diào)用irecv接口進行接收,在RDMA的實現(xiàn)中對應(yīng)的是將本端DMA的地址通過ncclIbSendFifo RDMA_WRITE至發(fā)送端
發(fā)送端進行發(fā)送時,sendProxyProgress調(diào)用isend接口進行發(fā)送,在RDMA中對應(yīng)的是從ncclIbSendFifo中獲取接收端的DMA地址,將上層的data直接RDMA_WRITE至接收端的DMA地址中
接收端維護本地的remFifoTail游標,每次接收時游標后移一位,接收端會將idx設(shè)置為一個自增的索引,同時將上層的DMA地址通過ncclIbSendFifo攜帶給發(fā)送端
發(fā)送端維護本地的fifoHead游標,每次發(fā)送后游標后移一位,發(fā)送端檢查fifo中元素的idx值是否為預(yù)期索引來判斷該fifo是否已經(jīng)被接收端設(shè)置過,即接收端的DMA地址已經(jīng)可以寫入
struct ncclIbSendFifo { uint64_t addr; int size; uint32_t rkey; uint32_t nreqs; uint32_t tag; uint64_t idx; }; // 發(fā)送端 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.4Libvma及SMC-R方式
除了上述修改業(yè)務(wù)源碼的方案,業(yè)內(nèi)也有“零入侵”業(yè)務(wù)程序的方案,比如libvma及smc-r方式。
SMC-R:
smc-r(SMC over RDMA)是IBM在2017提交至linux kernel的一種兼容socket層,使用共享內(nèi)存技術(shù)、基于RDMA技術(shù)實現(xiàn)的高性能內(nèi)核網(wǎng)絡(luò)協(xié)議棧。smc-r的主要實現(xiàn)是在內(nèi)核態(tài)實現(xiàn)了一個新的af_smc協(xié)議族,基于RDMA verbs接口實現(xiàn)內(nèi)核proto_ops中的各方法。
smc-r支持fallback回退機制,在通信雙方最開始建立連接時是使用tcp握手(特定的tcp選項)進行協(xié)商是否雙方均支持SMC-R能力,當(dāng)協(xié)商不成功時fallback為原始的tcp通信。完成協(xié)議協(xié)商并建立連接后,協(xié)議棧為SMC-R socket分配一塊用于緩存待發(fā)送數(shù)據(jù)的環(huán)形緩沖區(qū)sndbuf和一塊用于緩存待接收數(shù)據(jù)的環(huán)形緩沖區(qū)RMB(Remote Memory Buffer)。
發(fā)送端應(yīng)用程序通過socket接口將待發(fā)送數(shù)據(jù)拷貝到本側(cè)sndbuf中,由SMC-R協(xié)議棧通過RDMA WRITE操作直接高效地寫入對側(cè)節(jié)點的RMB中。同時伴隨著使用RDMA SEND/RECV操作交互連接數(shù)據(jù)管理消息,用于更新、同步環(huán)形緩沖區(qū)中的數(shù)據(jù)游標。
接收端SMC-R協(xié)議棧感知到RMB中填入新數(shù)據(jù)后,通過epoll等方式告知接收端應(yīng)用程序?qū)MB中的數(shù)據(jù)拷貝到用戶態(tài),完成數(shù)據(jù)傳輸。所以在SMC-R中,RMB充當(dāng)傳輸過程中的共享內(nèi)存。
圖 smc-r發(fā)送接收(轉(zhuǎn)自阿里云)
下面是一個基于smc-r通信的實際測試場景的協(xié)商交互抓包:
Libvma:
Libvma是Mellanox公司開源的一款高性能的用戶態(tài)網(wǎng)絡(luò)協(xié)議棧,它將socket的相關(guān)接口全部在用戶態(tài)空間實現(xiàn),實現(xiàn)對內(nèi)核的旁路,使用RDMA verbs接口直接調(diào)用網(wǎng)卡驅(qū)動,從而節(jié)省了大量的上下文數(shù)據(jù)拷貝,節(jié)省了 CPU 的資源降低了時延,業(yè)務(wù)在使用libvma時只需要使用LD_PRELOAD libvma.so替換原有的系統(tǒng)調(diào)用即可完成傳輸協(xié)議的替換。
Libvma內(nèi)部在tcp協(xié)議棧的實現(xiàn)上使用了lwip方案,重寫了epoll,使用了hugepage,內(nèi)部使用單獨的線程去輪詢RDMA CQ事件等方案,相較于內(nèi)核協(xié)議棧的實現(xiàn),在主機側(cè)的處理延遲有200%至500%的降低。
此外,在實際測試過程中發(fā)現(xiàn)libvma雖然使用的是RDMA verbs接口,但實際針對Mellanox mlx5系列驅(qū)動的網(wǎng)卡是直接用戶態(tài)驅(qū)動網(wǎng)卡,發(fā)送的仍然是原始基于tcp的以太報文,并不是rocev2的報文,具體討論可以見github上的issue參考[15]。
下面是基于libvma測試redis的場景,由于libvma bypass協(xié)議棧,并且重寫了epoll等其它特性,性能提升大概3倍:
總結(jié):
相較于業(yè)務(wù)使用raw verbs進行源碼修改,libvma及smc-r方式可以提供“零入侵、零修改”源碼的優(yōu)勢,但由于應(yīng)用程序在將數(shù)據(jù)提供給socket接口時仍然存在一次拷貝,所以性能上對比verbs方案來說有一定的損耗,對于想快速驗證RDMA能力的業(yè)務(wù)是一個不錯的POC驗證方式。
目前阿里云的Alibaba Cloud Linux3默認支持smc-r能力,結(jié)合阿里云的eRDMA能力網(wǎng)卡,可以使業(yè)務(wù)進行透明無損的RDMA傳輸替換,減少cpu的使用率,降低一定的通信延時。但目前該能力在阿里云上屬于公測能力,生產(chǎn)穩(wěn)定性待驗證,參考[14]。
libvma方式?jīng)]有l(wèi)inux社區(qū)的支持,并且更多的是針對Mellanox系列網(wǎng)卡的支持,在工業(yè)界使用的場景也不太多,目前在金融的高頻交易領(lǐng)域有一些使用嘗試。
05
總結(jié)與展望
前面主要分析和調(diào)研了一些開源應(yīng)用在進行業(yè)務(wù)適配RDMA傳輸?shù)姆桨?,整體來看RDMA改造的方案是分為兩部分,分別為通信接口的改造以及RDMA內(nèi)存管理設(shè)計。 通信接口改造主要指將tcp socket的傳輸接口修改為ib verbs或者cm接口,這部分同時涉及到適配現(xiàn)有業(yè)務(wù)網(wǎng)絡(luò)事件的處理模型。 由于RDMA傳輸數(shù)據(jù)時,需要預(yù)先將內(nèi)存注冊到HCA卡上,所以RDMA內(nèi)存管理會比較復(fù)雜,同時也是性能高低與否的關(guān)鍵。
1)數(shù)據(jù)傳輸時申請內(nèi)存,并進行內(nèi)存注冊,再進行RDMA操作。顯然這種模式在代碼實現(xiàn)上最為簡單,但是性能及效率最低,現(xiàn)有方案中很少有在fast path中使用這種內(nèi)存管理方案。
2)提前注冊好一大塊內(nèi)存,在上層業(yè)務(wù)需要發(fā)送數(shù)據(jù)時,將數(shù)據(jù)拷貝至RDMA注冊好的內(nèi)存。這種模式性能相較第一種有提升,但存在一定的內(nèi)存拷貝。
3)使用內(nèi)存池,業(yè)務(wù)及RDMA的內(nèi)存使用同一塊。性能明顯是最優(yōu)的,但是實現(xiàn)邏輯較復(fù)雜,需要管理好內(nèi)存的申請及釋放、某些實現(xiàn)中通信雙方也需要做內(nèi)存使用量的協(xié)商。
結(jié)合前面應(yīng)用的RDMA方案,匯總?cè)缦卤恚?/p>
應(yīng)用名稱 | 網(wǎng)絡(luò)處理模型 | 內(nèi)存方案 | 其他特性 |
Redis (pr stage) |
1.適配原有的單線程reactor非阻塞模式 2.rdma無pollout時間,在業(yè)務(wù)邏輯中額外處理 3.網(wǎng)絡(luò)支持插件式,不同的傳輸模式實現(xiàn)相同的網(wǎng)絡(luò)方法 |
1.預(yù)注冊內(nèi)存,RDMA Write模式 2.DMA地址通過控制消息交互 3.應(yīng)用與RDMA之間存在拷貝 |
1.有控制面交互,如xferbuffer 2.控制面信息復(fù)用RDMA通道 |
Tensorflow | 異步發(fā)送、阻塞接收 |
1.RDMA Write模式 2.應(yīng)用及RDMA共享內(nèi)存池 3.通信雙方通過消息交互DMA地址 |
1.使用基于TCP的GRPC通道進行RDMA鏈接的協(xié)商 2.有控制面消息交互,如metadata更新 3.控制面信息復(fù)用RDMA通道 |
BRPC | reactor模式 |
1.內(nèi)存池模式 2.使用RDMA Send模式 |
1.額外的流控機制 2.事件聚合 |
NCCL |
1.reactor模式 2.網(wǎng)絡(luò)支持插件式,不同的傳輸模式實現(xiàn)相同的網(wǎng)絡(luò)方法 |
1.RDMA Write模式 2.通信雙方通過一個共享fifo來交互具體的DMA地址 3.DMA地址是預(yù)先注冊的內(nèi)存 |
1.RDMA建立階段使用TCP鏈接進行協(xié)商 |
隨著AI的火熱,國產(chǎn)DPU、GPU的高速發(fā)展,數(shù)據(jù)中心內(nèi)在高性能計算、機器學(xué)習(xí)、分布式存儲等場景下的業(yè)務(wù)也需要隨著硬件能力的提升去適配使用這些能力,RDMA因其諸多優(yōu)點目前已經(jīng)廣泛被應(yīng)用。調(diào)研學(xué)習(xí)現(xiàn)有的方案是為了更好地適配及修改自研的業(yè)務(wù),相信隨著越來越多業(yè)務(wù)場景下RDMA的使用,其相關(guān)生態(tài)及應(yīng)用方案也會越來越成熟。
審核編輯:劉清
-
處理器
+關(guān)注
關(guān)注
68文章
19259瀏覽量
229651 -
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11529 -
RDMA
+關(guān)注
關(guān)注
0文章
77瀏覽量
8945 -
TCP通信
+關(guān)注
關(guān)注
0文章
146瀏覽量
4221 -
TLS
+關(guān)注
關(guān)注
0文章
44瀏覽量
4248
原文標題:RDMA在典型場景下的技術(shù)應(yīng)用分析與探索
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論