現在,服務器的網絡帶寬愈來愈高。當網絡帶寬邁過萬兆這條線後,操做系統用於處理網絡IO的開銷就愈來愈難以忽視。在一些網絡IO密集的業務中,操做系統自己成爲了網絡通訊的瓶頸,這不只會致使調用時延的增長(尤爲是長尾),還會影響到服務的總體吞吐。git
相對於網絡帶寬的發展速度,CPU性能的發展停滯是致使上述問題的主要緣由。 所以,想從根本上解決CPU參與網絡傳輸的低效問題,就要更多地藉助專用芯片的能力,RDMA高性能網絡勢不可擋。 github
RDMA(Remote Direct Memory Access),能夠簡單理解爲網卡徹底繞過CPU實現兩個服務器之間的內存數據交換。其做爲一種硬件實現的網絡傳輸技術,能夠大幅提高網絡傳輸效率,幫助網絡IO密集的業務(好比分佈式存儲、分佈式數據庫等)得到更低的時延以及更高的吞吐。數據庫
具體來講,RDMA技術的應用要藉助支持RDMA功能的網卡以及相應的驅動程序。由下圖所示,一旦應用程序分配好資源,其能夠直接把要發送的數據所在的內存地址和長度信息交給網卡。網卡從內存中拉取數據,由硬件完成報文封裝,而後發送給對應的接收端。接收端收到RDMA報文後,直接由硬件解封裝,取出數據後,直接放在應用程序預先指定的內存位置。apache
**因爲整個IO過程無需CPU參與,無需操做系統內核參與,沒有系統調用,沒有中斷,也無需內存拷貝,所以RDMA網絡傳輸能夠作到極高的性能。**在極限benchmark測試中,RDMA的時延能夠作到1us級別,而吞吐甚至能夠達到200G。編程
**須要注意的是,****RDMA的使用須要應用程序的代碼配合(RDMA編程)。**與傳統TCP傳輸不一樣,RDMA並無提供socket API封裝,而是要經過verbs API來調用(使用libibverbs)。出於避免中間層額外開銷的考慮,verbs API採用了貼近硬件實現的語義形態,致使使用方法與socket API差別巨大。 所以,對大多數開發者來講,不管是改造原有應用程序適配RDMA,仍是寫一個全新的RDMA原生的應用程序,都不容易。服務器
如圖所示,在socket API中,發送接收數據主要用到的接口以下:網絡
Socket API多線程
其中,write和read操做中的fd是標識一個鏈接的文件描述符。應用程序要發送的數據,會經過write拷貝到系統內核緩衝區中;而read實際上也是從系統內核緩衝區中將數據拷貝出來。 在絕大多數應用程序裏,一般都會設置fd爲非阻塞的,也就是說,若是系統內核緩衝區已滿,write操做會直接返回;而若是系統內核緩衝區爲空,read操做也會直接返回。 爲了在第一時間獲知內核緩衝區的狀態變化,應用程序須要epoll機制來監聽EPOLLIN和EPOLLOUT事件。若是epoll_wait函數因這些事件返回,則能夠觸發下一次的write和read操做。這就是socket API的基本使用方法。 做爲對比,在verbs API中,發送接收數據主要用到的接口以下:框架
Verbs API機器學習
其中,ibv_是libibverbs庫中函數和結構體的前綴。ibv_post_send近似於發送操做,ibv_post_recv近似於接收操做。發送接收操做裏面的qp(queue pair)近似於socket API裏面的fd,做爲一個鏈接對應的標識。wr(work request)這個結構體裏包含了要發送/接收的數據所在的內存地址(進程的虛擬地址)和長度。ibv_poll_cq則做爲事件檢測機制存在,相似於epoll_wait。
乍一看去,RDMA編程彷佛很簡單,只要把上述函數替換了就能夠。但事實上,上述的對應關係都是近似、相似,而不是等價。 關鍵區別在於,socket API都是同步操做,而RDMA API都是異步操做(注意異步和非阻塞是兩個不一樣的概念)。
具體而言,ibv_post_send函數返回成功,僅僅意味着成功地向網卡提交了發送請求,並不保證數據真的被髮送出去了。若是此時立馬對發送數據所在的內存進行寫操做,那麼發送出去的數據就極可能是不正確的。socket API是同步操做,write函數返回成功,意味着數據已經被寫入了內核緩衝區,雖然此時數據未必真的發送了,但應用程序已經能夠隨意處置發送數據所在的內存。
另外一方面,ibv_poll_cq所獲取的事件,與epoll_wait獲取的事件也是不一樣的。前者代表,以前提交給網卡的某一發送或接收請求完成了;然後者表示,有新的報文被成功發送或是接收了。這些語義上的變化會影響到上層應用程序的內存使用模式和API調用方式。
除了同步、異步的語義區別外, RDMA編程還有一個關鍵要素,即全部參與發送、接收的數據,所在的內存必須通過註冊。
所謂內存註冊,簡單理解,就是把一段內存的虛擬地址和物理地址間的映射關係綁定好之後註冊給網卡硬件。這麼作的緣由在於,發送請求和接收請求所提交的內存地址,都是虛擬地址。只有完成內存註冊,網卡才能把請求中的虛擬地址翻譯成物理地址,才能跳過CPU作直接內存訪問。內存註冊(以及解註冊)是一個很慢的操做,實際應用中一般須要構建內存池,經過一次性註冊、重複使用來避免頻繁調用註冊函數。
關於RDMA編程,還有不少細節是普通網絡編程所不關心的(好比流控、TCP回退、非中斷模式等等),這裏就不一一展開介紹了。 總而言之,RDMA編程並非一件容易的事情。那麼,如何才能讓開發者快速用上RDMA這樣的高性能網絡技術呢?
上面說了不少socket API和verbs API的對比,主要仍是爲了陪襯RDMA編程自己的複雜度。事實上,在實際生產環境中,直接調用socket API進行網絡傳輸的業務並很少,絕大多數都經過rpc框架間接使用socket API。一個完整的rpc框架,須要提供一整套網絡傳輸的解決方案,包括數據序列化、錯誤處理、多線程處理等等。 brpc是百度開源的一個基於C++的rpc框架,其相對於grpc更適用於有高性能需求的場景。在傳統TCP傳輸之外,brpc也提供了RDMA的使用方式,以進一步突破操做系統自己的性能限制。有關具體的實現細節,感興趣的朋友能夠參見github上的源碼(https://github.com/apache/incubator-brpc/tree/rdma)。
brpc client端使用RDMA
brpc server端使用RDMA
上面分別列出了brpc中client和server使用RDMA的方法,即在channel和server建立時,把use_rdma這個option設置爲true便可(默認是false,也就是使用TCP)。
是的,只需這兩行代碼。若是你的應用程序自己基於brpc構建,那麼從TCP遷移到RDMA,幾分鐘就夠了。固然,在上述quick start以後,若是有更高級的調優需求,brpc也提供了一些運行時的flag參數可調整,好比內存池大小、qp/cq的大小、輪詢替代中斷等等。
下面經過echo benchmark說明brpc使用RDMA的性能收益(能夠在github代碼中的rdma_performance目錄下找到該benchmark)。在25G網絡的測試環境中,對於2KB如下的消息,使用RDMA後,server端的最大QPS提升了50%以上,200k的QPS下平均時延降低了50%以上。
Echo benchmark下server端最大QPS(25G網絡)
Echo benchmark在200k QPS下的平均時延(25G網絡)
RDMA對echo benchmark的性能收益僅做爲參考。實際應用程序的workload與echo相差巨大。對於某些業務,使用RDMA的收益可能會小於上述值,由於網絡部分的開銷僅佔到其業務開銷的一部分。但對於另外一些業務,因爲避免了內核操做對業務邏輯的干擾,使用RDMA的收益甚至會高於上述值。這裏舉兩個應用brpc的例子:
在百度分佈式塊存儲業務中,使用RDMA相比於使用TCP,4KB fio時延測試的平均值降低了約30%(RDMA僅優化了網絡IO,存儲IO則不受RDMA影響)。
在百度分佈式內存KV業務中,使用RDMA相比於使用TCP,200k QPS下,單次查詢30key的平均時延降低了89%,而99分位時延降低了96%。
RDMA是一種新興的高性能網絡技術,對於通訊兩端都可控的數據中心內網絡IO密集業務,如HPC、機器學習、存儲、數據庫等,意義重大。咱們鼓勵相關業務的開發者關注RDMA技術,嘗試利用brpc構建本身的應用以平滑遷移至RDMA。 可是,須要指出的是,RDMA當前還沒法像TCP那樣通用,有一些基礎設施層面的限制須要注意:
RDMA須要網卡硬件支持。常見的萬兆網卡通常不支持這項技術。
RDMA的正常使用有賴於物理網絡支持。
百度智能雲在RDMA基礎設施方面積累了深厚的經驗。得益於先進的硬件配置與強大的工程技術,用戶能夠在25G甚至是100G網絡環境中,經過物理機或容器,充分獲取RDMA技術帶來的性能收益,而將與業務無關的複雜的物理網絡配置工做(好比無損網絡PFC以及顯示擁塞通告ECN),交給百度智能雲技術支持人員。歡迎對高性能計算、高性能存儲等業務有需求的開發者向百度智能雲諮詢相關狀況。
本文做者李兆耕,百度系統部資深研發工程師,長期關注高性能網絡技術,負責百度RDMA研發工做,覆蓋從上層業務調用到底層硬件支持的全技術棧。
本文由百度開發者中心發佈,一個面向開發者的知識分享平臺,專一於爲開發者打造一個有溫度的技術交流社區,開發者經過平臺來分享知識、相互交流。 developer 發佈!