1. 程式人生 > 其它 >圖解|深入理解Linux高效能網路架構的那些事!

圖解|深入理解Linux高效能網路架構的那些事!

1. 落寞的小黑

上週北京很冷,週五晚上大白下班奔地鐵站,收到了好基友小黑的微信:於是大白掉頭掃了個單車奔五道口了,小黑靠譜地選了個不錯的位置。

小黑: 你今天下班挺早呀!
大白: 就咱這覺悟,心裡有工作,哪裡都是辦公桌,不要拘泥於形式嘛。

明顯能感覺得到小黑哥最近好像比較累,之前眼裡bulingbuling閃的光是看不到了。

大白: 下午去面的哪家?啥崗位?咋樣?
小黑: 是一家做自動駕駛的創業公司,網站是看團隊介紹還不錯,就去看看了,這次沒咋準備,很多問題其實都熟悉,但是回答的不到位。
大白: 哦,明白了,那就是當時理解的不到位,稀裡糊塗過去了,現在忽然問起來,想不起重點。
小黑: 差不多吧,問我都做過哪些高效能的網路框架模型,也就是IO和事件驅動那一套。

話說完,小黑喝了一大口啤酒,大白看出了小黑心裡有一些落寞。畢竟在帝都這個地方競爭和工作壓力,以及生活瑣事都一直圍繞著我們,但是金錢和好運都巧妙地避開了自己...

想到這裡,大白也深深喝了一大口,我命由我不由天,開整!

大白:黑哥,你說這個問題確實不好回答,全是術語和略帶歧義的東西,我覺得我們抓住本質去闡述就好。
小黑:來,請開始你的表演,我學習學習。

大白決定和小黑好好聊聊,Linux開發中常用的高效能網路框架中的一些事兒,火鍋的映襯下讓夜色和天氣都不那麼寒冷了。通過本文你將會了解到以下內容:

  • IO事件和IO複用
  • 執行緒模型和事件驅動模型的架構
  • 基於事件驅動的Reactor模式詳解
  • 同步IO和非同步IO簡介

2. IO事件和IO複用

2.1 什麼是IO事件

IO指的是輸入Input/輸出Output,但是從漢語角度來說,出和入是相對的,所以我們需要個參照物。這裡我們的參照物選擇為程式執行時的主儲存空間,外部通常包括網絡卡、磁碟等。有了上述的設定理解起來就方便多了,我們來一起看下:

IO的本質是資料的流動,資料可以從網絡卡到程式記憶體,也可以從程式記憶體寫到網絡卡,磁碟操作也是如此。

所以可以把常見的IO分為:

  • 網路IO:記憶體和網絡卡的資料互動
  • 檔案IO:記憶體和磁碟的資料互動

那什麼又是IO事件呢?事件可以理解為一種狀態或者動作,也就是狀態的遷移會觸發一種相應的動作。網路IO的事件通常包括:

  • 可讀事件
  • 可寫事件
  • 異常事件

理解可讀可寫事件是非常有必要的,一般來說一個socket大部分時候是可寫的,但是並不是都可讀。可讀一般代表是一個新連線或者原有連線有新資料互動,對於服務端程式來說也是重點關注的事件。

2.2 什麼是IO複用

設想假如有幾萬個IO事件,那麼應用程式該如何管理呢?這就要提到IO複用了。IO複用從本質上來說就是應用程式藉助於IO複用函式向核心註冊很多型別的IO事件,當這些註冊的IO事件發生變化時核心就通過IO複用函式來通知應用程式。從圖中可以看到,IO複用中複用的就是一個負責監聽管理這些IO事件的執行緒。之所以可以實現一個執行緒管理成百上千個IO事件,是因為大部分時間裡某個時刻只有少量IO事件被觸發。大概就像這樣:草原上的一隻大狗可以看管幾十只綿羊,因為大部分時候只有個別綿羊不守規矩亂跑,其他的都是乖乖吃草

3. 網路框架設計要素

要理解網路框架有哪些,必須要清楚網路框架完成了哪些事情。大致描述下這個請求處理的流程:

  • 遠端的機器A傳送了一個HTTP請求到伺服器B,此時伺服器B網絡卡接收到資料併產生一個IO可讀事件;
  • 我們以同步IO為例,此時核心將該可讀事件通知到應用程式的Listen執行緒;
  • Listen執行緒將任務甩給Handler執行緒,由Handler將資料從核心讀緩衝區拷貝到使用者空間讀緩衝區;
  • 請求資料包在應用程式內部進行計算和處理並封裝響應包;
  • Handler執行緒等待可寫事件的到來;
  • 當這個連線可寫時將資料從使用者態寫緩衝區拷貝到核心緩衝區,並通過網絡卡傳送出去;
備註:上述例子是以同步IO為例,並且將執行緒中的角色分為Listen執行緒、Handler執行緒、Worker執行緒,分別完成不同的工作,後續會詳細展開。

所以我們可以知道,要完成一個數據互動,涉及了幾大塊內容:

  • IO事件監聽
  • 資料拷貝
  • 資料處理和計算

大白認為,這三大塊內容,不論什麼形式的框架都繞不開,也是理解網路架構的關鍵所在。

4. 高效能網路框架實踐

4.1 基於執行緒模型

在早期併發數不多的場景中,有一種One Request One Thread的架構模式。該模式下每次接收一個新請求就建立一個處理執行緒,執行緒雖然消耗資源並不多,但是成千上萬請求打過來,效能也是扛不住的。這是一種比較原始的架構,思路也非常清晰,建立多個執行緒來提供處理能力,但在高併發生產環境中幾乎沒有應用,本文不再展開。

4.2 基於事件驅動模型

當前流行的是基於事件驅動的IO複用模型,相比多執行緒模型優勢很明顯。在此我們先理解一下什麼是事件驅動Event-Drive-Model。

事件驅動程式設計是一種程式設計正規化,程式的執行流由外部事件來決定,它的特點是包含一個事件迴圈,當外部事件發生時使用回撥機制來觸發相應的處理。

通俗來說就是:有一個迴圈裝置在一直等待各種事件的到來,並將到達的事件放到佇列中,再由一個分揀裝置來呼叫對應的處理裝置來響應

4.3 Reactor反應堆模式

第一次聽到這個模式的時候很困惑,究竟反應堆是個啥?研究了一下發現,反應堆是個核物理的概念,大致是這個樣子的:

核反應堆是核電站的心臟 ,它的工作原理是這樣的:原子由原子核與核外電子組成,原子核由質子與中子組成。
當鈾235的原子核受到外來中子轟擊時,一個原子核會吸收一箇中子分裂成兩個質量較小的原子核,同時放出2-3箇中子。
這裂變產生的中子又去轟擊另外的鈾235原子核,引起新的裂變如此持續進行就是裂變的鏈式反應。

結合這種核裂變的圖,好像是一個請求打過來,伺服器內部瞬間延伸出很多分支來完成響應,一變二,二變四,甚至更多,確實有種反應堆的感覺。接下來我們看看究竟反應堆模式是如何構建高效能QQ號碼出售地圖網路框架的。

5.反應堆模式詳解

反應堆模式是一種思想,形式卻有很多種。

5.1 反應堆模式的本質是什麼

從本質上理解,無論什麼網路框架都要完成兩部分操作:

  • IO操作:資料包的讀取和寫入
  • CPU操作:資料請求的處理和封裝

所以上述這些問題由誰來做以及多少執行緒來做,就衍生出了很多形式,所以不要被表面現象迷惑,出現必有原因,追溯之後我們才能真正掌握它。反應堆模式根據處理IO環節和處理資料環節的數量差異分為如下幾種:

  • 單Reactor執行緒
  • 單Reactor執行緒和執行緒池
  • 多Reactor執行緒和執行緒池

我們來看看這三種常見模式的特點、原理、優缺點、應用場景等。

5.2 單Reactor執行緒模式

這種模式最為簡潔,一個執行緒完成了連線的監聽、接收新連線、處理連線、讀取資料、寫入資料全套工作。由於只使用了一個執行緒,對於多核利用率偏低,但是程式設計簡單。是不是覺得這個種單執行緒的模式沒有市場?那可未必,不信你看Redis。在這種模式種IO操作和CPU操作是沒有分開的,都是由1個執行緒來完成的,顯然如果在Handler處理某個請求超時了將會阻塞客戶端的正常連線。在Redis中由於都是記憶體操作,速度很快,這種瓶頸雖然存在但是不夠明顯。

5.3 單Reactor執行緒和執行緒池模式

為了解決IO操作和CPU操作的不匹配,也就是IO操作和CPU操作是在一個執行緒內部序列執行的,這樣就拉低了CPU操作效率。一種解決方法就是將IO操作和CPU操作分別由單獨的執行緒來完成,各玩各的互不影響。單Reactor執行緒完成IO操作、複用工作執行緒池來完成CPU操作就是一種解決思路。在這種模式種由Reactor執行緒完成連線的管理和資料讀取&寫回,完全掌管IO操作。工作執行緒池處理來自上游分發的任務,對其中的資料進行解碼、計算、編碼再返回給Reactor執行緒和客戶端完成互動。這種模式有效利用了多核,但是單Reactor執行緒來完成IO操作在高併發場景中仍然會出現瓶頸。換句話說,連線實在太多了,一個Reactor執行緒忙不過來建立新連線和響應舊連線這些事情,因此Reactor執行緒也需要幾個幫手。

5.4 多Reactor執行緒和執行緒池模式

水平擴充套件往往是提供效能的有效方法。我們將Reactor執行緒進行擴充套件,一個Reactor執行緒負責處理新連線,多個Reactor執行緒負責處理連線成功的IO資料讀寫。也就是進一步將監聽&建立連線 和 處理連線 分別由兩個及以上的執行緒來完成,進一步提高了IO操作部分的效率。這種模式算是比較高配的版本了,在實際生產環境也有使用。

5.5 拓展:同步IO和非同步IO

我們可以輕易區分什麼是阻塞IO和非阻塞IO,那麼什麼是同步IO和非同步IO呢?前面提到Reactor模式其中非常重要的一環就是呼叫read/write函式來完成資料拷貝,這部分是應用程式自己完成的,核心只負責通知監控的事件到來了,所以本質上Reactor模式屬於非阻塞同步IO。還有一種Preactor模式,藉助於系統本身的非同步IO特性,由作業系統進行資料拷貝,在完成之後來通知應用程式來取就可以,效率更高一些,但是底層需要藉助於核心的非同步IO機制來實現。底層的非同步IO機制可能借助於DMA和Zero-Copy技術來實現,理論上效能更高。當前Windows系統通過IOCP實現了真正的非同步I/O,而在Linux 系統的非同步I/O還不完善,比如Linux中的boost.asio模組就是非同步IO的支援,但是目前Linux系統還是以基於Reactor模式的非阻塞同步IO為主。

6. 小結

本文從IO事件和IO複用出發,闡述了網路架構最底層的組成。繼續展開了基於執行緒模型和基於事件驅動模型的網路框架特點及其設計要素。之後重點描述了反應堆模式的核心本質,以及生產環境中的多種形式。最後簡單介紹了同步IO和非同步IO的區別,以及Preactor模式的優勢。希望讀者朋友可以摒棄專業術語和表述,抓住問題的本質和重點,找到一個適合自己思維方法去理解和掌握高效能網路架構的設計之道。或許,高效能網路框架只是一個紙老虎。