https://www.yuque.com/dhcn/tn/xlk00g
這篇文章承接上篇Linux低延遲服務器系統調優,主要談談Linux網絡IO的低延遲方案。由於本人經驗所限,只使用過solarflare的軟硬件方案,沒用過其他的kernel bypass框架(如DPDK)或網卡,所以本文只局限於solarflare相關的使用經驗。
首先聲明下,本人和solarflare公司沒有利益關系,本文盡可能客觀的分享個人的使用感受。另外我也不是solarflare產品的專家,如有不准確之處還望指正。
solarflare的拳頭產品是它的高性能網卡。我對solareflare網卡的第一感覺是“貴”,和一塊高端服務器CPU的價格差不多了。因此如果只把它當成一塊支持10G/25G網絡的普通網卡來用是暴殄天物了,這里說的當成普通網卡指的是不使用它的軟件方案,而是使用Linux內核實現網絡編程,這樣的話它並不會比普通網卡快多少。如上篇文章所言“一個對延遲要求很高(比如個位數微秒級延遲)的實時任務是不能觸碰內核的”,solarflare網卡提供了配套的kernel bypass軟件解決方案,還不止一個:Onload, ef_vi和Tcpdirect(關於這三個stack的用法這里就不細說了,官方文檔說的很詳細)。首先我們關心的是它們的性能怎么樣?下圖是官方文檔中的一個測試結果:
Latency test results
測試使用了兩台直接連接的軟硬件配置相同的服務器進行pingpong測試,這樣RTT的一半可以認為是一台機器從收到發的網卡到網卡的延遲。

從測試結果看來這個數字還挺吸引人的:10G網絡可以低至800多ns。不過我認為這個測試方法有些脫離實際應用:首先測試中每次發送的數據都是完全一樣的(payload全0),在ef_vi udp的測試代碼中甚至出現了這種優化:初始化階段就准備好了要發送的frame數據(包括ethernet頭,ip頭,udp頭和payload),只要程序一收到數據就發送這個相同的frame出去,有點作弊的成分。另外,pingpong測試是不間斷的收發,一秒鍾會處理幾十萬個包,實際場景一般不會出現這么高頻的情況。
因此,我決定實現一個更貼合實際的測試:echo測試,echo client和echo server程序分別在兩台服務器上,echo client每次發送不同的數據,echo server收到后原樣轉發給client,這里echo server是under test的機器。echo server的收和發使用的是不同的協議:收udp multicast(模仿接收行情),發送tcp數據(模仿發送訂單),因此echo server會先tcp連接到echo client。echo client控制發送頻率:每秒1000個包,5秒結束。
通過測試不同的收發stack組合,以及各種配置選項,我所能達到的最佳延遲是960ns +/- 90ns(這里除去了第一個echo的延遲,因為這個簡單的測試沒做cache warmup,第一個延遲會高出5000ns左右)。最佳方案使用了最新的X2系列網卡(支持ctpio,比非ctpio快200ns),ef_vi接收udp,tcpdirect發送tcp,以及上篇文章提到的屏蔽中斷的內核。這個測試主要使用16字節payload的小包進行測試,對於更長的包可以用1 byte = 1.5ns來近似(親測)。下面簡單談談我對3個stack的使用感受。
onload
這是solarflare最經典的kernel bypass stack,屬於transparent kernel bypass,因為它提供了兼容socket網絡編程的接口,用戶不需要修改自己的代碼,只需preload libonload.so即可使用(類似使用tcmalloc)。由於其特別的易用性,十分適合新人入坑(先要買塊網卡),是運動手表界的iwatch。
關於性能,在我的測試中onload比使用kernel的傳統方法快了6000多ns,比ef_vi/tcpdirect慢300多ns,算是不錯了。
另外提一下onload的配置,onload提供了非常多的配置選項,我在測試中測試了大部分相關的配置,最后發現對絕大多數配置來說使用默認值就好了,例外是設置EF_TCP_FASTSTART_INIT=0 EF_TCP_FASTSTART_IDLE=0 會稍微降低些延遲,這也和推薦的latency profile一致。
ef_vi
ef_vi是一個level 2的底層API,可以收發原始的ethernet frame,但沒有提供上層協議的支持(不過能支持基於IP,proto,port的接收端filter)。除此之外ef_vi最大的特點是zero-copy:它要求用戶預先分配一些recv buffer提供給ef_vi使用,網卡收到包后會直接寫入這些buffer,用戶通過eventq poll接口獲得已填充數據的buffer id即可開始處理接收數據,處理完后再把buffer交還ef_vi循環使用。相比而言,onload無法做到這樣的zero-copy,因為socket API只在用戶開始接收數據時才提供buffer。
ef_vi API使用起來比較復雜,且缺乏上層協議支持,但能提供最好的性能,是專業用戶的選擇。我建議只使用ef_vi做udp的接收端,因為經filter后用戶對包頭的解析工作不多,這還有一個好處是,可以在用戶程序中抓包(比如用於記錄行情,后面會提到通用的本地抓包方法並不合適):通過一個ring buffer和一個寫pcap文件的非關鍵線程,可以做到處理網絡數據和抓包同時進行,而且抓包對處理數據的關鍵線程幾乎零影響,這充分利用了ef_vi底層和zero-copy的特性。
tcpdirect
tcpdirect是基於ef_vi並實現了上層協議的stack,提供了類似socket的API:zocket,能讓用戶讀寫tcp/udp的payload數據,同時也繼承了ef_vi zero-copy的特性。另外,tcpdirct要求使用huge pages。
我的建議是,對於udp的發送端和整個tcp使用tcpdirect。
最后,談一個比較重要的話題:如何測量網卡到網卡的延遲?在這個echo測試中,由於echo server和echo client是不對等的的測試者(從收發的協議上),而且由於條件所限,兩台機器的硬件配置有一定差異,也沒有通過網線直連(中間經過了交換機),所以光看RTT不可靠,只能使用通用的網絡延遲測試方法:
1)讓交換機把echo server的收發鏈路數據鏡像到另一個抓包設備。這種方法比較依賴交換機性能,精確度不高,比如抓包結果會出現因果亂序的現象。
2)通過tap或分光設備把echo server的網口數據分發到另一個抓包設備。由於條件所限,暫時無法使用...
3)在echo server本地抓包。這個比較依賴抓包工具,我進行過嘗試,最后發現目前已有的工具都有其局限性,后面會詳細討論。
4)在程序中使用onload/ef_vi/tcpdirect提供的API獲得包收發的硬件時間戳(網卡時間戳),這是我主要使用的方法,同時也參考了從echo client獲得的RTT,用於double check前者的結果,另外可以測量各種抓包工具或者獲取硬件時間戳造成的額外延遲。
本人經驗有限,如果有更好的方法歡迎在評論區探討。最后談談我使用過的本地記錄網絡包時間戳的方法:
tcpdump
這是大家用過的工具,通過kernel抓包,但對kernel bypass的方案無效,而且記錄的是軟件時間(系統時間戳),不等同網卡時間。tcpdump帶來的額外延遲約為2000ns。
onload_tcpdump
顧名思義,可以對使用onload方案的網絡數據進行抓包,但對其他無效,也無法獲得硬件時間戳。onload_tcpdump帶來的額外延遲約為1500ns。
solar_capture
solarflare自己軟件抓包工具,需要額外購買license,不便宜,和網卡本身的價格差不多了,而且license綁定網卡。官方宣傳solar_capture性能很高,可以記錄收發包的網卡時間戳。我們曾經寄予厚望買了一個license試用,但發現局限性很大,基本個玩具。
首先,solar_capture不支持solarflare自己的最先進的X2系列網卡,只支持較老的7000/8000系列。關於這個問題我前幾天有機會問了solarflare的一個system architect,他說更新solar_capture是一個較低優先級的事情...看來這工具本身在公司內部就是個邊緣產品(官網上找solarCapture看到的都是硬件設備,這個軟件工具的信息很難找到)。
另外,solar_capture不支持"ultra-low-latency"的網卡模式。這又是個硬傷,因為"ultra-low-latency"比其他模式快100ns左右(親測),是我們默認會使用的模式。
最后,solar_capture對於一個網口的收和發的數據是通過兩個任務來記錄的,pcap文件中可能出現因果亂序現象(但時間戳是准確的),需要分析結果的用戶自己處理。
solar_capture帶來的額外延遲約為50ns。
通過ef_vi/tcpdirect API獲取網卡時間戳
現在ef_vi和tcpdirect提供了獲取接收和發送時間戳的API(獲取發送時間戳是OpenOnload 201811新增加的功能),這樣就可以在用戶程序中直接測量網卡到網卡的延時了。獲取兩個硬件時間戳帶來的額外延遲約為50ns,看上去和獲取系統時間戳的開銷差不多(相關文章:Linux獲取納秒時間戳的正確方式)。
sfptpd
這其實是solarflare提供的一個同步網卡時間源的工具,可以和網絡上的其他設備同步,不過我主要用來和本地系統時間同步,這樣可以測量網卡到用戶程序,和用戶程序到網卡的分段延遲。使用sfptpd的一個不方便的地方是這個服務必須一直運行才能保證同步的比較准確。
