背景回複"書",擷取
背景回複“k8s”,可領取k8s資料
一、網絡IO的處境和趨勢
從我們使用者的使用就可以感受到網速一直在提升,而網絡技術的發展也從1GE/10GE/25GE/40GE/100GE的演變,從中可以得出單機的網絡IO能力必須跟上時代的發展。
1. 傳統的電信領域
IP層及以下,例如路由器、交換機、防火牆、基站等裝置都是采用硬體解決方案。基于專用網絡處理器(NP),有基于FPGA,更有基于ASIC的。但是基于硬體的劣勢非常明顯,發生Bug不易修複,不易調試維護,并且網絡技術一直在發展,例如2G/3G/4G/5G等移動技術的革新,這些屬于業務的邏輯基于硬體實作太痛苦,不能快速疊代。傳統領域面臨的挑戰是急需一套軟體架構的高性能網絡IO開發架構。
2. 雲的發展
私有雲的出現通過網絡功能虛拟化(NFV)共享硬體成為趨勢,NFV的定義是通過标準的伺服器、标準交換機實作各種傳統的或新的網絡功能。急需一套基于常用系統和标準伺服器的高性能網絡IO開發架構。
3. 單機性能的飙升
網卡從1G到100G的發展,CPU從單核到多核到多CPU的發展,伺服器的單機能力通過橫行擴充達到新的高點。但是軟體開發卻無法跟上節奏,單機處理能力沒能和硬體門當戶對,如何開發出與時并進高吞吐量的服務,單機百萬千萬并發能力。
即使有業務對QPS要求不高,主要是CPU密集型,但是現在大資料分析、人工智能等應用都需要在分布式伺服器之間傳輸大量資料完成作業。這點應該是我們網際網路背景開發最應關注,也最關聯的。
二、Linux + X86網絡IO瓶頸
在數年前曾經寫過《網卡工作原理及高并發下的調優》一文,描述了Linux的收發封包流程。根據經驗,在C1(8核)上跑應用每1W包處理需要消耗1%軟中斷CPU,這意味着單機的上限是100萬PPS(Packet Per Second)。
從TGW(Netfilter版)的性能100萬PPS,AliLVS優化了也隻到150萬PPS,并且他們使用的伺服器的配置還是比較好的。假設,我們要跑滿10GE網卡,每個包64位元組,這就需要2000萬PPS(注:以太網萬兆網卡速度上限是1488萬PPS,因為最小幀大小為84B《Bandwidth, Packets Per Second, and Other Network Performance Metrics》),100G是2億PPS,即每個包的處理耗時不能超過50納秒。
而一次Cache Miss,不管是TLB、資料Cache、指令Cache發生Miss,回記憶體讀取大約65納秒,NUMA體系下跨Node通訊大約40納秒。是以,即使不加上業務邏輯,即使純收發包都如此艱難。我們要控制Cache的命中率,我們要了解計算機體系結構,不能發生跨Node通訊。
從這些資料,我希望可以直接感受一下這裡的挑戰有多大,理想和現實,我們需要從中平衡。問題都有這些
- 1.傳統的收發封包方式都必須采用硬中斷來做通訊,每次硬中斷大約消耗100微秒,這還不算因為終止上下文所帶來的Cache Miss。
- 2.資料必須從核心态使用者态之間切換拷貝帶來大量CPU消耗,全局鎖競争。
- 3.收發包都有系統調用的開銷。
- 4.核心工作在多核上,為可全局一緻,即使采用Lock Free,也避免不了鎖總線、記憶體屏障帶來的性能損耗。
- 5.從網卡到業務程序,經過的路徑太長,有些其實未必要的,例如netfilter架構,這些都帶來一定的消耗,而且容易Cache Miss。
三、DPDK的基本原理
從前面的分析可以得知IO實作的方式、核心的瓶頸,以及資料流過核心存在不可控因素,這些都是在核心中實作,核心是導緻瓶頸的原因所在,要解決問題需要繞過核心。是以主流解決方案都是旁路網卡IO,繞過核心直接在使用者态收發包來解決核心的瓶頸。
Linux社群也提供了旁路機制Netmap,官方資料10G網卡1400萬PPS,但是Netmap沒廣泛使用。其原因有幾個:
1.Netmap需要驅動的支援,即需要網卡廠商認可這個方案。
2.Netmap仍然依賴中斷通知機制,沒完全解決瓶頸。
3.Netmap更像是幾個系統調用,實作使用者态直接收發包,功能太過原始,沒形成依賴的網絡開發架構,社群不完善。
那麼,我們來看看發展了十幾年的DPDK,從Intel主導開發,到華為、思科、AWS等大廠商的加入,核心玩家都在該圈子裡,擁有完善的社群,生态形成閉環。
早期,主要是傳統電信領域3層以下的應用,如華為、中國電信、中國移動都是其早期使用者,交換機、路由器、網關是主要應用場景。但是,随着上層業務的需求以及DPDK的完善,在更高的應用也在逐漸出現。DPDK旁路原理:

左邊是原來的方式資料從 網卡 -> 驅動 -> 協定棧 -> Socket接口 -> 業務。
右邊是DPDK的方式,基于UIO(Userspace I/O)旁路資料。資料從 網卡 -> DPDK輪詢模式-> DPDK基礎庫 -> 業務
使用者态的好處是易用開發和維護,靈活性好。并且Crash也不影響核心運作,魯棒性強。
DPDK支援的CPU體系架構:x86、ARM、PowerPC(PPC)
DPDK支援的網卡清單:https://core.dpdk.org/supported/,我們主流使用Intel 82599(光口)、Intel x540(電口)
四、DPDK的基石UIO
為了讓驅動運作在使用者态,Linux提供UIO機制。使用UIO可以通過read感覺中斷,通過mmap實作和網卡的通訊,要開發使用者态驅動有幾個步驟:
UIO原理:
- 1.開發運作在核心的UIO子產品,因為硬中斷隻能在核心處理
- 2.通過/dev/uioX讀取中斷
- 3.通過mmap和外設共享記憶體
五、DPDK核心優化:PMD
DPDK的UIO驅動屏蔽了硬體發出中斷,然後在使用者态采用主動輪詢的方式,這種模式被稱為PMD(Poll Mode Driver)。
UIO旁路了核心,主動輪詢去掉硬中斷,DPDK進而可以在使用者态做收發包處理。帶來Zero Copy、無系統調用的好處,同步處理減少上下文切換帶來的Cache Miss。
運作在PMD的Core會處于使用者态CPU100%的狀态。
網絡空閑時CPU長期空轉,會帶來能耗問題。是以,DPDK推出Interrupt DPDK模式。
Interrupt DPDK:
它的原理和NAPI很像,就是沒包可處理時進入睡眠,改為中斷通知。并且可以和其他程序共享同個CPU Core,但是DPDK程序會有更高排程優先級。
六、DPDK的高性能代碼實作
1. 采用HugePage減少TLB Miss
預設下Linux采用4KB為一頁,頁越小記憶體越大,頁表的開銷越大,頁表的記憶體占用也越大。CPU有TLB(Translation Lookaside Buffer)成本高是以一般就隻能存放幾百到上千個頁表項。如果程序要使用64G記憶體,則64G/4KB=16000000(一千六百萬)頁,每頁在頁表項中占用16000000 * 4B=62MB。如果用HugePage采用2MB作為一頁,隻需64G/2MB=2000,數量不在同個級别。
而DPDK采用HugePage,在x86-64下支援2MB、1GB的頁大小,幾何級的降低了頁表項的大小,進而減少TLB-Miss。并提供了記憶體池(Mempool)、MBuf、無鎖環(Ring)、Bitmap等基礎庫。根據我們的實踐,在資料平面(Data Plane)頻繁的記憶體配置設定釋放,必須使用記憶體池,不能直接使用rte_malloc,DPDK的記憶體配置設定實作非常簡陋,不如ptmalloc。
2. SNA(Shared-nothing Architecture)
軟體架構去中心化,盡量避免全局共享,帶來全局競争,失去橫向擴充的能力。NUMA體系下不跨Node遠端使用記憶體。
3. SIMD(Single Instruction Multiple Data)
從最早的mmx/sse到最新的avx2,SIMD的能力一直在增強。DPDK采用批量同時處理多個包,再用向量程式設計,一個周期内對所有包進行處理。比如,memcpy就使用SIMD來提高速度。
SIMD在遊戲背景比較常見,但是其他業務如果有類似批量處理的場景,要提高性能,也可看看能否滿足。
4. 不使用慢速API
這裡需要重新定義一下慢速API,比如說gettimeofday,雖然在64位下通過vDSO已經不需要陷入核心态,隻是一個純記憶體通路,每秒也能達到幾千萬的級别。但是,不要忘記了我們在10GE下,每秒的處理能力就要達到幾千萬。是以即使是gettimeofday也屬于慢速API。DPDK提供Cycles接口,例如rte_get_tsc_cycles接口,基于HPET或TSC實作。
在x86-64下使用RDTSC指令,直接從寄存器讀取,需要輸入2個參數,比較常見的實作:
這麼寫邏輯沒錯,但是還不夠極緻,還涉及到2次位運算才能得到結果,我們看看DPDK是怎麼實作:
巧妙的利用C的union共享記憶體,直接指派,減少了不必要的運算。但是使用tsc有些問題需要面對和解決
- 1) CPU親和性,解決多核跳動不精确的問題
- 2) 記憶體屏障,解決亂序執行不精确的問題
- 3) 禁止降頻和禁止Intel Turbo Boost,固定CPU頻率,解決頻率變化帶來的失準問題
5. 編譯執行優化
1) 分支預測
現代CPU通過pipeline、superscalar提高并行處理能力,為了進一步發揮并行能力會做分支預測,提升CPU的并行能力。遇到分支時判斷可能進入哪個分支,提前處理該分支的代碼,預先做指令讀取編碼讀取寄存器等,預測失敗則預處理全部丢棄。我們開發業務有時候會非常清楚這個分支是true還是false,那就可以通過人工幹預生成更緊湊的代碼提示CPU分支預測成功率。
2) CPU Cache預取
Cache Miss的代價非常高,回記憶體讀需要65納秒,可以将即将通路的資料主動推送的CPU Cache進行優化。比較典型的場景是連結清單的周遊,連結清單的下一節點都是随機記憶體位址,是以CPU肯定是無法自動預加載的。但是我們在處理本節點時,可以通過CPU指令将下一個節點推送到Cache裡。
…等等
3) 記憶體對齊
記憶體對齊有2個好處:
l 避免結構體成員跨Cache Line,需2次讀取才能合并到寄存器中,降低性能。結構體成員需從大到小排序和以及強制對齊。
l 多線程場景下寫産生False sharing,造成Cache Miss,結構體按Cache Line對齊。
4) 常量優化
常量相關的運算的編譯階段完成。比如C++11引入了constexp,比如可以使用GCC的__builtin_constant_p來判斷值是否常量,然後對常量進行編譯時得出結果。舉例網絡序主機序轉換
其中rte_constant_bswap32的實作
5)使用CPU指令
現代CPU提供很多指令可直接完成常見功能,比如大小端轉換,x86有bswap指令直接支援了。
對實作的追求不一樣,是以造輪子前一定要先了解好輪子。
Google開源的cpu_features可以擷取目前CPU支援什麼特性,進而對特定CPU進行執行優化。高性能程式設計永無止境,對硬體、核心、編譯器、開發語言的了解要深入且與時俱進。
七、DPDK生态
對我們網際網路背景開發來說DPDK架構本身提供的能力還是比較裸的,比如要使用DPDK就必須實作ARP、IP層這些基礎功能,有一定上手難度。如果要更高層的業務使用,還需要使用者态的傳輸協定支援。不建議直接使用DPDK。
目前生态完善,社群強大(一線大廠支援)的應用層開發項目是FD.io(The Fast Data Project),有思科開源支援的VPP,比較完善的協定支援,ARP、VLAN、Multipath、IPv4/v6、MPLS等。使用者态傳輸協定UDP/TCP有TLDK。從項目定位到社群支援力度算比較靠譜的架構。
想知道更多?掃描下面的二維碼關注我