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 單精度 ...