Linux網路之 從 C10K 到 DPDK

2022-07-15 08:30:10 字數 3584 閱讀 5758

c10k 和 c1000k 的首字母 c 是 client 的縮寫。c10k 就是單機同時處理 1 萬個請求(併發連線 1 萬)的問題,而 c1000k 也就是單機支援處理 100 萬個請求(併發連線 100 萬)的問題。

i/o 的模型,在 c10k 以前,linux 中網路處理都用同步阻塞的方式,也就是每個請求都分配乙個程序或者執行緒。請求數只有 100 個時,這種方式自然沒問題,但增加到 10000 個請求時,10000 個程序或執行緒的排程、上下文切換乃至它們占用的記憶體,都會成為瓶頸。

檔案描述符

使用套接字介面的時候,是要分配乙個檔案描述符,然後後續所有的i/o都通過這個檔案描述符來操作(包括io模型中要判斷可讀寫狀態)。

兩種 i/o 事件通知的方式:水平觸發和邊緣觸發,它們常用在套接字介面的檔案描述符中。

根據水平觸發原理,select 和 poll 需要從檔案描述符列表中,找出哪些可以執行 i/o ,然後進行真正的網路 i/o 讀寫。由於 i/o 是非阻塞的,乙個執行緒中就可以同時監控一批套接字的檔案描述符,這樣就達到了單執行緒處理多請求的目的。

這種方式的最大優點,是對應用程式比較友好,它的 api 非常簡單。

select 使用固定長度的位相量,表示檔案描述符的集合,因此會有最大描述符數量的限制。比如,在 32 位系統中,預設限制是 1024。並且,在 select 內部,檢查套接字狀態是用輪詢的方法,再加上應用軟體使用時的輪詢,就變成了乙個 o(n^2) 的關係。

而 poll 改進了 select 的表示方法,換成了乙個沒有固定長度的陣列,這樣就沒有了最大描述符數量的限制(當然還會受到系統檔案描述符限制)。但應用程式在使用 poll 時,同樣需要對檔案描述符列表進行輪詢,這樣,處理耗時跟描述符數量就是 o(n) 的關係。

除此之外,應用程式每次呼叫 select 和 poll 時,還需要把檔案描述符的集合,從使用者空間傳入核心空間,由核心修改後,再傳出到使用者空間中。這一來一回的核心空間與使用者空間切換,也增加了處理成本。

epoll 使用紅黑樹,在核心中管理檔案描述符的集合,這樣,就不需要應用程式在每次操作時都傳入、傳出這個集合。

epoll 使用事件驅動的機制,只關注有 i/o 事件發生的檔案描述符,不需要輪詢掃瞄整個集合。

非同步 i/o 允許應用程式同時發起很多 i/o 操作,而不用等待這些操作完成。而在 i/o 完成後,系統會用事件通知(比如訊號或者**函式)的方式,告訴應用程式。這時,應用程式才會去查詢 i/o 操作的結果。

使用 i/o 多路復用後,就可以在乙個程序或執行緒中處理多個請求,其中,又有下面兩種不同的工作模型。

主程序 + 多個 worker 子程序,這也是最常用的一種模型

通用工作模式就是:

比如,最常用的反向**伺服器 nginx 就是這麼工作的。它也是由主程序和多個 worker 程序組成。主程序主要用來初始化套接字,並管理子程序的生命週期;而 worker 程序,則負責實際的請求處理。

這些 worker 程序,實際上並不需要經常建立和銷毀,而是在沒任務時休眠,有任務時喚醒。只有在 worker 由於某些異常退出時,主程序才需要建立新的程序來代替它。(也可以用執行緒代替程序)

監聽到相同埠的多程序模型

在這種方式下,所有的程序都監聽相同的介面,並且開啟 so_reuseport 選項,由核心負責將請求負載均衡到這些監聽程序中去。

從物理資源使用上來說,100 萬個請求需要大量的系統資源。比如,

從軟體資源上來說,大量的連線也會占用大量的軟體資源,比如檔案描述符的數量、連線狀態的跟蹤(conntrack)、網路協議棧的快取大小(比如套接字讀寫快取、tcp 讀寫快取)等等。

大量請求帶來的中斷處理,也會帶來非常高的處理成本。這樣,就需要多佇列網絡卡、中斷負載均衡、cpu 繫結、rps/rfs(軟中斷負載均衡到多個 cpu 核上),以及將網路包的處理解除安裝(offload)到網路裝置(如 tso/gso、lro/gro、vxlan offload)等各種硬體和軟體的優化。

c1000k 的解決方法,本質上還是構建在 epoll 的非阻塞 i/o 模型上。只不過,除了 i/o 模型之外,還需要從應用程式到 linux 核心、再到 cpu、記憶體和網路等各個層次的深度優化,特別是需要借助硬體,來解除安裝那些原來通過軟體處理的大量功能。

無論怎麼優化應用程式和核心中的各種網路引數,想實現 1000 萬請求的併發,都是極其困難的。

究其根本,還是 linux 核心協議棧做了太多太繁重的工作。從網絡卡中斷帶來的硬中斷處理程式開始,到軟中斷中的各層網路協議處理,最後再到應用程式,這個路徑實在是太長了,就會導致網路包的處理優化,到了一定程度後,就無法更進一步了。

要解決這個問題,最重要就是跳過核心協議棧的冗長路徑,把網路包直接送到要處理的應用程式那裡去。這裡有兩種常見的機制,dpdk 和 xdp。

dpdk

dpdk是使用者態網路的標準。它跳過核心協議棧,直接由使用者態程序通過輪詢的方式,來處理網路接收。

dpdk 通過大頁、cpu 繫結、記憶體對齊、流水線併發等多種機制,優化網路包的處理效率。

xdpxdp(express data path),則是 linux 核心提供的一種高效能網路資料路徑。它允許網路包,在進入核心協議棧之前,就進行處理,也可以帶來更高的效能。xdp 底層跟我們之前用到的 bcc-tools 一樣,都是基於 linux 核心的 ebpf 機制實現的。

xdp 對核心的要求比較高,需要的是 linux 4.8 以上版本,並且它也不提供快取佇列。基於 xdp 的應用程式通常是專用的網路應用,常見的有 ids(入侵檢測系統)、ddos 防禦、 cilium 容器網路外掛程式等。小結

c10k 問題的根源,一方面在於系統有限的資源;另一方面,也是更重要的因素,是同步阻塞的 i/o 模型以及輪詢的套接字介面,限制了網路事件的處理效率。linux 2.6 中引入的 epoll ,完美解決了 c10k 的問題,現在的高效能網路方案都基於 epoll。

從 c10k 到 c100k ,可能只需要增加系統的物理資源就可以滿足;但從 c100k 到 c1000k ,就不僅僅是增加物理資源就能解決的問題了。這時,就需要多方面的優化工作了,從硬體的中斷處理和網路功能解除安裝、到網路協議棧的檔案描述符數量、連線狀態跟蹤、快取佇列等核心的優化,再到應用程式的工作模型優化,都是考慮的重點。

再進一步,要實現 c10m ,就不只是增加物理資源,或者優化核心和應用程式可以解決的問題了。這時候,就需要用 xdp 的方式,在核心協議棧之前處理網路包;或者用 dpdk 直接跳過網路協議棧,在使用者空間通過輪詢的方式直接處理網路包。

學習筆記

整理自極客時間

快速入門C 1

大家最為熟悉的就是各種hello world了吧 今天我們就從這裡開始講解 include iostream using namespace std int main include 包含 引用 所以吶,這就是引用庫之類的標頭檔案 h iostream是指iostream庫,iostream的意思是...

遊戲開發第二站之C (1)

防衛式宣告 在乙個標頭檔案中,往往很少有人 小白初學者 編寫標頭檔案時加入防衛式宣告,如complex.h complex.h ifndef complex define complex 標頭檔案主體 endif 在預編譯階段,編譯器會把.件展開。防衛式宣告的作用是 防止由於同乙個標頭檔案被包含多次...

C 1 資料型別

分為兩類 1.基本資料型別 2.引用資料型別 基本資料型別 1整數型 有負數的 sbyte,short int long 正整數 byte,ushort,uint,ulong 可能會表現的不夠形象,看下圖 2.浮點數 float 適用於較小的浮點數 在後面加f 例 float a 1.0f 單精度 ...