高性能IO模型分析-Reactor模式和Proactor模式(二)

I/O模型的應用:Reactor模式和Proactor模式


上一章內容是本章內容的理論基礎和底層依賴。本章內容則是在上章內容作爲底層的基礎,經過巧妙的設計和前赴後繼的實踐,得出的一套應用層的“最佳實踐”。雖不是開箱即用,但也爲我們提供了很大的便利,讓我們少走很多彎路。下面我們就看看有哪些不錯的架構模型、模式值得我們去參考。

在web服務中,處理web請求通常有兩種體系結構,分別爲:thread-based architecture(基於線程的架構)、event-driven architecture(事件驅動模型)

一、thread-based architecture(基於線程的架構)

thread-based architecture(基於線程的架構),通俗的說就是:多線程併發模式,一個連接一個線程,服務器每當收到客戶端的一個請求, 便開啓一個獨立的線程來處理。
多線程併發這種模式一定程度上極大地提高了服務器的吞吐量,由於在不同線程中,之前的請求在read阻塞以後,不會影響到後續的請求。但是,僅適用於於併發量不大的場景,因爲:

  • 線程需要佔用一定的內存資源
  • 創建和銷燬線程也需一定的代價
  • 操作系統在切換線程也需要一定的開銷
  • 線程處理I/O,在等待輸入或輸出的這段時間處於空閒的狀態,同樣也會造成cpu資源的浪費

如果連接數太高,系統將無法承受

二、event-driven architecture(事件驅動模型)

事件驅動體系結構是目前比較廣泛使用的一種。這種方式會定義一系列的事件處理器來響應事件的發生,並且將服務端接受連接對事件的處理分離。其中,事件是一種狀態的改變。比如,tcp中socket的new incoming connection、ready for read、ready for write。

如果對event-driven architecture有深入興趣,可以看下維基百科對它的解釋:傳送門

Reactor模式和Proactor模式都是是event-driven architecture(事件驅動模型)的實現方式,下面聊一聊這兩種模式。

2.1 Reactor模式

維基百科對Reactor pattern的解釋:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers

從這個描述中,我們知道Reactor模式首先是事件驅動的,有一個或多個併發輸入源,有一個Service Handler,有多個Request Handlers;Service Handler會對輸入的請求(Event)進行多路複用,並同步地將它們分發給相應的Request Handler。

下面的圖將直觀地展示上述文字描述:
Reactor_Simple

Reactor模式也有三種不同的方式,下面一一介紹。

2.1.1 Reactor模式-單線程模式

Java中的NIO模式的Selector網絡通訊,其實就是一個簡單的Reactor模型。可以說是單線程的Reactor模式
Reactor-單線程模式Reactor的單線程模式的單線程主要是針對於I/O操作而言,也就是所以的I/O的accept()、read()、write()以及connect()操作都在一個線程上完成的。

但在目前的單線程Reactor模式中,不僅I/O操作在該Reactor線程上,連非I/O的業務操作也在該線程上進行處理了,這可能會大大延遲I/O請求的響應。所以我們應該將非I/O的業務邏輯操作從Reactor線程上卸載,以此來加速Reactor線程對I/O請求的響應。

2.1.2 Reactor模式-工作者線程池模式

與單線程模式不同的是,添加了一個工作者線程池,並將非I/O操作從Reactor線程中移出轉交給工作者線程池(Thread Pool)來執行。這樣能夠提高Reactor線程的I/O響應,不至於因爲一些耗時的業務邏輯而延遲對後面I/O請求的處理。
Reactor工作者線程模式

在工作者線程池模式中,雖然非I/O操作交給了線程池來處理,但是所有的I/O操作依然由Reactor單線程執行,在高負載、高併發或大數據量的應用場景,依然較容易成爲瓶頸。所以,對於Reactor的優化,又產生出下面的多線程模式。

2.1.3 Reactor模式-多線程模式

對於多個CPU的機器,爲充分利用系統資源,將Reactor拆分爲兩部分:mainReactor和subReactor
Reactor多線程模式
mainReactor負責監聽server socket,用來處理網絡新連接的建立,將建立的socketChannel指定註冊給subReactor,通常一個線程就可以處理 ;

subReactor維護自己的selector, 基於mainReactor 註冊的socketChannel多路分離I/O讀寫事件,讀寫網絡數據,通常使用多線程

對非I/O的操作,依然轉交給工作者線程池(Thread Pool)執行。

此種模型中,每個模塊的工作更加專一,耦合度更低,性能和穩定性也大量的提升,支持的可併發客戶端數量可達到上百萬級別。關於此種模型的應用,目前有很多優秀的框架已經在應用了,比如mina和netty 等。Reactor模式-多線程模式下去掉工作者線程池(Thread Pool),則是Netty中NIO的默認模式。

  • mainReactor對應Netty中配置的BossGroup線程組,主要負責接受客戶端連接的建立。一般只暴露一個服務端口,BossGroup線程組一般一個線程工作即可
  • subReactor對應Netty中配置的WorkerGroup線程組,BossGroup線程組接受並建立完客戶端的連接後,將網絡socket轉交給WorkerGroup線程組,然後在WorkerGroup線程組內選擇一個線程,進行I/O的處理。WorkerGroup線程組主要處理I/O,一般設置2*CPU核數個線程

2.2 Proactor模式

流程與Reactor模式類似,區別在於proactor在IO ready事件觸發後,完成IO操作再通知應用回調。雖然在linux平臺還是基於epoll/select,但是內部實現了異步操作處理器(Asynchronous Operation Processor)以及異步事件分離器(Asynchronous Event Demultiplexer)將IO操作與應用回調隔離。經典應用例如boost asio異步IO庫的結構和流程圖如下:
Proactor
再直觀一點,就是下面這幅圖:

Proactor模式

再再直觀一點,其實就回到了五大模型-異步I/O模型的流程,就是下面這幅圖:

proactor2針對第二幅圖在稍作解釋:

Reactor模式中,用戶線程通過向Reactor對象註冊感興趣的事件監聽,然後事件觸發時調用事件處理函數。而Proactor模式中,用戶線程將AsynchronousOperation(讀/寫等)、Proactor以及操作完成時的CompletionHandler註冊到AsynchronousOperationProcessor。

AsynchronousOperationProcessor使用Facade模式提供了一組異步操作API(讀/寫等)供用戶使用,當用戶線程調用異步API後,便繼續執行自己的任務。AsynchronousOperationProcessor 會開啓獨立的內核線程執行異步操作,實現真正的異步。當異步IO操作完成時,AsynchronousOperationProcessor將用戶線程與AsynchronousOperation一起註冊的Proactor和CompletionHandler取出,然後將CompletionHandler與IO操作的結果數據一起轉發給Proactor,Proactor負責回調每一個異步操作的事件完成處理函數handle_event。雖然Proactor模式中每個異步操作都可以綁定一個Proactor對象,但是一般在操作系統中,Proactor被實現爲Singleton模式,以便於集中化分發操作完成事件。

2.3 Reactor模式和Proactor模式的總結對比

2.3.1 主動和被動

以主動寫爲例:

  • Reactor將handler放到select(),等待可寫就緒,然後調用write()寫入數據;寫完處理後續邏輯;
  • Proactor調用aoi_write後立刻返回,由內核負責寫操作,寫完後調用相應的回調函數處理後續邏輯

Reactor模式是一種被動的處理,即有事件發生時被動處理。而Proator模式則是主動發起異步調用,然後循環檢測完成事件。

2.3.2 實現

Reactor實現了一個被動的事件分離和分發模型,服務等待請求事件的到來,再通過不受間斷的同步處理事件,從而做出反應;

Proactor實現了一個主動的事件分離和分發模型;這種設計允許多個任務併發的執行,從而提高吞吐量。

所以涉及到文件I/O或耗時I/O可以使用Proactor模式,或使用多線程模擬實現異步I/O的方式。

2.3.3 優點

Reactor實現相對簡單,對於鏈接多,但耗時短的處理場景高效;

  • 操作系統可以在多個事件源上等待,並且避免了線程切換的性能開銷和編程複雜性;
  • 事件的串行化對應用是透明的,可以順序的同步執行而不需要加鎖;
  • 事務分離:將與應用無關的多路複用、分配機制和與應用相關的回調函數分離開來。

Proactor在理論上性能更高,能夠處理耗時長的併發場景。爲什麼說在理論上?請自行搜索Netty 5.X版本廢棄的原因。

2.3.4 缺點

Reactor處理耗時長的操作會造成事件分發的阻塞,影響到後續事件的處理;

Proactor實現邏輯複雜;依賴操作系統對異步的支持,目前實現了純異步操作的操作系統少,實現優秀的如windows IOCP,但由於其windows系統用於服務器的侷限性,目前應用範圍較小;而Unix/Linux系統對純異步的支持有限,應用事件驅動的主流還是通過select/epoll來實現。

2.3.5 適用場景

Reactor:同時接收多個服務請求,並且依次同步的處理它們的事件驅動程序;

Proactor:異步接收和同時處理多個服務請求的事件驅動程序。
ux系統對純異步的支持有限,應用事件驅動的主流還是通過select/epoll來實現。

2.3.5 適用場景

Reactor:同時接收多個服務請求,並且依次同步的處理它們的事件驅動程序;

Proactor:異步接收和同時處理多個服務請求的事件驅動程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章