理解高性能網絡模型

隨著互聯網的發展,面對海量用戶高并發業務,傳統的阻塞式的服務端架構模式已經無能為力,由此,本文旨在為大家提供有用的概覽以及網絡服務模型的比較,以揭開設計和實現高性能網絡架構的神秘面紗

1 服務端處理網絡請求

首先看看服務端處理網絡請求的典型過程:

服務端處理網絡請求流程圖

可以看到,主要處理步驟包括:

  • 1、獲取請求數據
    客戶端與服務器建立連接發出請求,服務器接受請求(1-3)
  • 2、構建響應
    當服務器接收完請求,并在用戶空間處理客戶端的請求,直到構建響應完成(4)
  • 3、返回數據
    服務器將已構建好的響應再通過內核空間的網絡I/O發還給客戶端(5-7)

設計服務端并發模型時,主要有如下兩個關鍵點:

  • 服務器如何管理連接,獲取輸入數據
  • 服務器如何處理請求

以上兩個關鍵點最終都與操作系統的I/O模型以及線程(進程)模型相關,下面詳細介紹這兩個模型

2 I/O模型

2.1 概念理論

介紹操作系統的I/O模型之前,先了解一下幾個概念:

  • 阻塞調用與非阻塞調用
    • 阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回
    • 非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程

兩者的最大區別在于被調用方在收到請求到返回結果之前的這段時間內,調用方是否一直在等待。阻塞是指調用方一直在等待而且別的事情什么都不做。非阻塞是指調用方先去忙別的事情

  • 同步處理與異步處理

    • 同步處理是指被調用方得到最終結果之后才返回給調用方
    • 異步處理是指被調用方先返回應答,然后再計算調用結果,計算完最終結果后再通知并返回給調用方
  • 阻塞、非阻塞和同步、異步的區別
    阻塞、非阻塞和同步、異步其實針對的對象是不一樣的:
    阻塞、非阻塞的討論對象是調用者
    同步、異步的討論對象是被調用者

  • recvfrom函數
    recvfrom函數(經socket接收數據),這里把它視為系統調用

一個輸入操作通常包括兩個不同的階段

  • 等待數據準備好
  • 從內核向進程復制數據

對于一個套接字上的輸入操作,第一步通常涉及等待數據從網絡中到達。當所等待分組到達時,它被復制到內核中的某個緩沖區。第二步就是把數據從內核緩沖區復制到應用進程緩沖區

實際應用程序在系統調用完成上面2步操作時,調用方式的阻塞、非阻塞,操作系統在處理應用程序請求時處理方式的同步、異步處理的不同,參考《UNIX網絡編程卷1》,可以分為5種I/O模型

2.2 阻塞式I/O模型(blocking I/O)

阻塞式I/O模型

簡介
在阻塞式I/O模型中,應用程序在從調用recvfrom開始到它返回有數據報準備好這段時間是阻塞的,recvfrom返回成功后,應用進程開始處理數據報

比喻
一個人在釣魚,當沒魚上鉤時,就坐在岸邊一直等

優點
程序簡單,在阻塞等待數據期間進程/線程掛起,基本不會占用CPU資源

缺點
每個連接需要獨立的進程/線程單獨處理,當并發請求量大時為了維護程序,內存、線程切換開銷較大,這種模型在實際生產中很少使用

2.3 非阻塞式I/O模型(non-blocking I/O)

非阻塞式I/O模型

簡介
在非阻塞式I/O模型中,應用程序把一個套接口設置為非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤,應用程序基于I/O操作函數將不斷的輪詢數據是否已經準備好,如果沒有準備好,繼續輪詢,直到數據準備好為止

比喻
邊釣魚邊玩手機,隔會再看看有沒有魚上鉤,有的話就迅速拉桿

優點
不會阻塞在內核的等待數據過程,每次發起的I/O請求可以立即返回,不用阻塞等待,實時性較好

缺點輪詢將會不斷地詢問內核,這將占用大量的CPU時間,系統資源利用率較低,所以一般Web服務器不使用這種I/O模型

2.4 I/O復用模型(I/O multiplexing)

I/O復用模型

簡介
在I/O復用模型中,會用到select或poll函數或epoll函數(Linux2.6以后的內核開始支持),這兩個函數也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作,而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數

比喻
放了一堆魚竿,在岸邊一直守著這堆魚竿,直到有魚上鉤

優點
可以基于一個阻塞對象,同時在多個描述符上等待就緒,而不是使用多個線程(每個文件描述符一個線程),這樣可以大大節省系統資源

缺點
當連接數較少時效率相比多線程+阻塞I/O模型效率較低,可能延遲更大,因為單個連接處理需要2次系統調用,占用時間會有增加

2.5 信號驅動式I/O模型(signal-driven I/O)

信號驅動式I/O模型

簡介
在信號驅動式I/O模型中,應用程序使用套接口進行信號驅動I/O,并安裝一個信號處理函數,進程繼續運行并不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據

比喻
魚竿上系了個鈴鐺,當鈴鐺響,就知道魚上鉤,然后可以專心玩手機

優點
線程并沒有在等待數據時被阻塞,可以提高資源的利用率

缺點

  • 信號I/O在大量IO操作時可能會因為信號隊列溢出導致沒法通知
  • 信號驅動I/O盡管對于處理UDP套接字來說有用,即這種信號通知意味著到達一個數據報,或者返回一個異步錯誤。但是,對于TCP而言,信號驅動的I/O方式近乎無用,因為導致這種通知的條件為數眾多,每一個來進行判別會消耗很大資源,與前幾種方式相比優勢盡失

2.6 異步I/O模型(asynchronous I/O)

異步I/O模型

簡介
由POSIX規范定義,應用程序告知內核啟動某個操作,并讓內核在整個操作(包括將數據從內核拷貝到應用程序的緩沖區)完成后通知應用程序。這種模型與信號驅動模型的主要區別在于:信號驅動I/O是由內核通知應用程序何時啟動一個I/O操作,而異步I/O模型是由內核通知應用程序I/O操作何時完成

優點
異步 I/O 能夠充分利用 DMA 特性,讓 I/O 操作與計算重疊

缺點
要實現真正的異步 I/O,操作系統需要做大量的工作。目前 Windows 下通過 IOCP 實現了真正的異步 I/O,而在 Linux 系統下,Linux2.6才引入,目前 AIO 并不完善,因此在 Linux 下實現高并發網絡編程時都是以 IO復用模型模式為主

2.5 5種I/O模型總結

從上圖中我們可以看出,可以看出,越往后,阻塞越少,理論上效率也是最優。其五種I/O模型中,前四種屬于同步I/O,因為其中真正的I/O操作(recvfrom)將阻塞進程/線程,只有異步I/O模型才于POSIX定義的異步I/O相匹配

3 線程模型

介紹完服務器如何基于I/O模型管理連接,獲取輸入數據,下面介紹基于進程/線程模型,服務器如何處理請求

值得說明的是,具體選擇線程還是進程,更多是與平臺及編程語言相關,例如C語言使用線程和進程都可以(例如Nginx使用進程,Memcached使用線程),Java語言一般使用線程(例如Netty),為了描述方便,下面都使用線程來進進行描述

3.1 傳統阻塞I/O服務模型

傳統阻塞I/O服務模型

特點

  • 采用阻塞式I/O模型獲取輸入數據
  • 每個連接都需要獨立的線程完成數據輸入,業務處理,數據返回的完整操作

存在問題

  • 當并發數較大時,需要創建大量線程來處理連接,系統資源占用較大
  • 連接建立后,如果當前線程暫時沒有數據可讀,則線程就阻塞在read操作上,造成線程資源浪費

3.2 Reactor模式

針對傳統傳統阻塞I/O服務模型的2個缺點,比較常見的有如下解決方案:

  • 基于I/O復用模型,多個連接共用一個阻塞對象,應用程序只需要在一個阻塞對象上等待,無需阻塞等待所有連接。當某條連接有新的數據可以處理時,操作系統通知應用程序,線程從阻塞狀態返回,開始進行業務處理
  • 基于線程池復用線程資源,不必再為每個連接創建線程,將連接完成后的業務處理任務分配給線程進行處理,一個線程可以處理多個連接的業務

I/O復用結合線程池,這就是Reactor模式基本設計思想

Reactor

Reactor模式,是指通過一個或多個輸入同時傳遞給服務處理器的服務請求的事件驅動處理模式。 服務端程序處理傳入多路請求,并將它們同步分派給請求對應的處理線程,Reactor模式也叫Dispatcher模式,即I/O多了復用統一監聽事件,收到事件后分發(Dispatch給某進程),是編寫高性能網絡服務器的必備技術之一

Reactor模式中有2個關鍵組成:

  • Reactor
    Reactor在一個單獨的線程中運行,負責監聽和分發事件,分發給適當的處理程序來對IO事件做出反應。 它就像公司的電話接線員,它接聽來自客戶的電話并將線路轉移到適當的聯系人

  • Handlers
    處理程序執行I/O事件要完成的實際事件,類似于客戶想要與之交談的公司中的實際官員。Reactor通過調度適當的處理程序來響應I/O事件,處理程序執行非阻塞操作

根據Reactor的數量和處理資源池線程的數量不同,有3種典型的實現:

  • 單Reactor單線程
  • 單Reactor多線程
  • 主從Reactor多線程

下面詳細介紹這3種實現

3.2.1 單Reactor單線程

單Reactor單線程

其中,select是前面I/O復用模型介紹的標準網絡編程API,可以實現應用程序通過一個阻塞對象監聽多路連接請求,其他方案示意圖類似

方案說明

  • Reactor對象通過select監控客戶端請求事件,收到事件后通過dispatch進行分發
  • 如果是建立連接請求事件,則由Acceptor通過accept處理連接請求,然后創建一個Handler對象處理連接完成后的后續業務處理
  • 如果不是建立連接事件,則Reactor會分發調用連接對應的Handler來響應
  • Handler會完成read->業務處理->send的完整業務流程

優點
模型簡單,沒有多線程、進程通信、競爭的問題,全部都在一個線程中完成

缺點

  • 性能問題:只有一個線程,無法完全發揮多核CPU的性能
    Handler在處理某個連接上的業務時,整個進程無法處理其他連接事件,很容易導致性能瓶頸
  • 可靠性問題:線程意外跑飛,或者進入死循環,會導致整個系統通信模塊不可用,不能接收和處理外部消息,造成節點故障

使用場景
客戶端的數量有限,業務處理非常快速,比如Redis,業務處理的時間復雜度O(1)

3.2.2 單Reactor多線程

單Reactor多線程

方案說明

  • Reactor對象通過select監控客戶端請求事件,收到事件后通過dispatch進行分發
  • 如果是建立連接請求事件,則由Acceptor通過accept處理連接請求,然后創建一個Handler對象處理連接完成后的續各種事件
  • 如果不是建立連接事件,則Reactor會分發調用連接對應的Handler來響應
  • Handler只負責響應事件,不做具體業務處理,通過read讀取數據后,會分發給后面的Worker線程池進行業務處理
  • Worker線程池會分配獨立的線程完成真正的業務處理,如何將響應結果發給Handler進行處理
  • Handler收到響應結果后通過send將響應結果返回給client

優點
可以充分利用多核CPU的處理能力

缺點

  • 多線程數據共享和訪問比較復雜
  • Reactor承擔所有事件的監聽和響應,在單線程中運行,高并發場景下容易成為性能瓶頸

3.2.3 主從Reactor多線程

針對單Reactor多線程模型中,Reactor在單線程中運行,高并發場景下容易成為性能瓶頸,可以讓Reactor在多線程中運行

主從Reactor多線程

方案說明

  • Reactor主線程MainReactor對象通過select監控建立連接事件,收到事件后通過Acceptor接收,處理建立連接事件
  • Accepto處理建立連接事件后,MainReactor將連接分配Reactor子線程給SubReactor進行處理
  • SubReactor將連接加入連接隊列進行監聽,并創建一個Handler用于處理各種連接事件
  • 當有新的事件發生時,SubReactor會調用連接對應的Handler進行響應
  • Handler通過read讀取數據后,會分發給后面的Worker線程池進行業務處理
  • Worker線程池會分配獨立的線程完成真正的業務處理,如何將響應結果發給Handler進行處理
  • Handler收到響應結果后通過send將響應結果返回給client

優點

  • 父線程與子線程的數據交互簡單職責明確,父線程只需要接收新連接,子線程完成后續的業務處理
  • 父線程與子線程的數據交互簡單,Reactor主線程只需要把新連接傳給子線程,子線程無需返回數據

這種模型在許多項目中廣泛使用,包括Nginx主從Reactor多進程模型,Memcached主從多線程,Netty主從多線程模型的支持

3.2.4 總結

3種模式可以用個比喻來理解:
餐廳常常雇傭接待員負責迎接顧客,當顧客入坐后,侍應生專門為這張桌子服務

  • 單Reactor單線程
    接待員和侍應生是同一個人,全程為顧客服務
  • 單Reactor多線程
    1個接待員,多個侍應生,接待員只負責接待
  • 主從Reactor多線程
    多個接待員,多個侍應生

Reactor模式具有如下的優點:

  • 響應快,不必為單個同步時間所阻塞,雖然Reactor本身依然是同步的
  • 編程相對簡單,可以最大程度的避免復雜的多線程及同步問題,并且避免了多線程/進程的切換開銷;
  • 可擴展性,可以方便的通過增加Reactor實例個數來充分利用CPU資源
  • 可復用性,Reactor模型本身與具體事件處理邏輯無關,具有很高的復用性

3.3 Proactor模型

在Reactor模式中,Reactor等待某個事件或者可應用或個操作的狀態發生(比如文件描述符可讀寫,或者是socket可讀寫),然后把這個事件傳給事先注冊的Handler(事件處理函數或者回調函數),由后者來做實際的讀寫操作,其中的讀寫操作都需要應用程序同步操作,所以Reactor是非阻塞同步網絡模型。如果把I/O操作改為異步,即交給操作系統來完成就能進一步提升性能,這就是異步網絡模型Proactor

Proactor

Proactor是和異步I/O相關的,詳細方案如下:

  • ProactorInitiator創建Proactor和Handler對象,并將Proactor和Handler都通過AsyOptProcessor(Asynchronous Operation Processor)注冊到內核
  • AsyOptProcessor處理注冊請求,并處理I/O操作
  • AsyOptProcessor完成I/O操作后通知Proactor
  • Proactor根據不同的事件類型回調不同的Handler進行業務處理
  • Handler完成業務處理

可以看出Proactor和Reactor的區別:Reactor是在事件發生時就通知事先注冊的事件(讀寫在應用程序線程中處理完成);Proactor是在事件發生時基于異步I/O完成讀寫操作(由內核完成),待I/O操作完成后才回調應用程序的處理器來處理進行業務處理

理論上Proactor比Reactor效率更高,異步I/O更加充分發揮DMA(Direct Memory Access,直接內存存取)的優勢,但是有如下缺點:

  • 編程復雜性
    由于異步操作流程的事件的初始化和事件完成在時間和空間上都是相互分離的,因此開發異步應用程序更加復雜。應用程序還可能因為反向的流控而變得更加難以Debug
  • 內存使用
    緩沖區在讀或寫操作的時間段內必須保持住,可能造成持續的不確定性,并且每個并發操作都要求有獨立的緩存,相比Reactor模式,在socket已經準備好讀或寫前,是不要求開辟緩存的
  • 操作系統支持
    Windows 下通過 IOCP 實現了真正的異步 I/O,而在 Linux 系統下,Linux2.6才引入,目前異步I/O還不完善

因此在Linux下實現高并發網絡編程都是以Reactor模型為主

參考

從0開始學架構 —— Alibaba技術專家李運華

Netty入門與實戰

技術: Linux網絡IO模型

多線程網絡服務模型

IO中的阻塞、非阻塞、同步、異步

UNIX網絡編程卷1:套接字聯網API(第3版)

異步網絡模型

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • 本文基于Netty4.1展開介紹相關理論模型,使用場景,基本組件、整體架構,知其然且知其所以然,希望給讀者提供學習...
    caison閱讀 2,907評論 0 13
  • Netty的簡單介紹 Netty 是一個 NIO client-server(客戶端服務器)框架,使用 Netty...
    AI喬治閱讀 8,429評論 1 101
  • 遇到的每一個人 可能是你的命中注定 也可能是你的命中不幸
    阿徐阿徐不靜閱讀 199評論 0 0
  • 倘若早知道結局是如此,你還會不會如飛蛾一樣,撲火得忘乎所以? 《最好的我們》里最令人心疼的就是簡單和路星河了吧?一...
    二姑娘兒閱讀 787評論 0 4