文章目錄
上一章內容是本章內容的理論基礎和底層依賴。本章內容則是在上章內容作爲底層的基礎,經過巧妙的設計和前赴後繼的實踐,得出的一套應用層的“最佳實踐”。雖不是開箱即用,但也爲我們提供了很大的便利,讓我們少走很多彎路。下面我們就看看有哪些不錯的架構模型、模式值得我們去參考。
在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模式也有三種不同的方式,下面一一介紹。
2.1.1 Reactor模式-單線程模式
Java中的NIO模式的Selector網絡通訊,其實就是一個簡單的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請求的處理。
在工作者線程池模式中,雖然非I/O操作交給了線程池來處理,但是所有的I/O操作依然由Reactor單線程執行,在高負載、高併發或大數據量的應用場景,依然較容易成爲瓶頸。所以,對於Reactor的優化,又產生出下面的多線程模式。
2.1.3 Reactor模式-多線程模式
對於多個CPU的機器,爲充分利用系統資源,將Reactor拆分爲兩部分:mainReactor和subReactor
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庫的結構和流程圖如下:
再直觀一點,就是下面這幅圖:
再再直觀一點,其實就回到了五大模型-異步I/O模型的流程,就是下面這幅圖:
針對第二幅圖在稍作解釋:
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:異步接收和同時處理多個服務請求的事件驅動程序。