【IO】Reactor模式

先看前兩篇:
一、IO的四種基本模式瞭解相關的概念:【IO】SBIO,SNBIO,ANIO,AIO
二、IO多路複用:【IO】IO多路複用及select,poll,epoll運行機制

接下來繼續總結Reactor模式。
IO多路複用只是簡單的介紹了在網絡請求中的前半部分,而後半部分的請求處理和響應就是接下來的reactor模式演化。

最最原始的網絡編程思路就是服務器用一個while循環,不斷監聽端口是否有新的套接字連接,如果有,那麼就調用一個處理函數處理。這樣做單線程阻塞嚴重。

後來考慮可以用多線程併發模式,這樣來一個請求創建一個線程進行處理。這樣可以大大提高服務器的吞吐量,但是線程創建和銷燬消耗了太多的資源,如果連接數很高系統性能也會受到很大影響。

改進方法:採用基於事件驅動的設計,單線程模式,當有事件觸發時,纔會調用處理器進行數據處理。使用Reactor模式,對線程的數量進行控制,一個線程處理大量的事件。

單線程模式Reactor

下圖來源於網絡,認爲更形象更好理解。
在這裏插入圖片描述
(1)Reactor:負責響應IO事件,當檢測到一個新的事件,將其發送給相應的Handler去處理;新的事件包含連接建立就緒、讀就緒、寫就緒等。

(2)Handler:將自身(handler)與事件綁定,負責事件的處理,完成channel的讀入,完成處理業務邏輯後,負責將結果寫出channel。acceptor是一個特殊的handler。

下圖取自“Scalable IO in Java”
在這裏插入圖片描述
事件流程:

1、服務器端的Reactor是一個線程對象,該線程會啓動事件循環,並使用Selector來實現IO的多路複用。
註冊一個Acceptor事件處理器到Reactor中,Acceptor事件處理器所關注的事件是ACCEPT事件,
這樣Reactor會監聽客戶端向服務器端發起的連接請求事件(ACCEPT事件)。

2、客戶端向服務器端發起一個連接請求,Reactor監聽到了該ACCEPT事件的發生並將該ACCEPT事件派發給相應的Acceptor處理器來進行處理。Acceptor處理器通過accept()方法得到與這個客戶端對應的連接(SocketChannel),
然後將該連接所關注的READ事件以及對應的READ事件處理器註冊到Reactor中,
這樣一來Reactor就會監聽該連接的READ事件了。或者當你需要向客戶端發送數據時,
就向Reactor註冊該連接的WRITE事件和其處理器。

3、當Reactor監聽到有讀或者寫事件發生時,將相關的事件派發給對應的處理器進行處理。
比如,讀處理器會通過SocketChannel的read()方法讀取數據,此時read()操作可以直接讀取到數據,
而不會堵塞與等待可讀的數據到來。

4、每當處理完所有就緒的感興趣的I/O事件後,Reactor線程會再次執行select()阻塞等待新的事件就緒
並將其分派給對應處理器進行處理。

缺點:
1、 當其中某個 handler 阻塞時, 會導致其他所有的 client 的 handler 都得不到執行, 並且更嚴重的是, handler 的阻塞也會導致整個服務不能接收新的 client 請求(因爲 acceptor 也被阻塞了)。 因爲有這麼多的缺陷, 因此單線程Reactor 模型用的比較少。這種單線程模型不能充分利用多核資源,所以實際使用的不多。

因此,單線程模型僅僅適用於handler 中業務處理組件能快速完成的場景。

改進:創建一個線程池,將非IO處理移交工作線程池處理。

工作者線程池Reactor

在線程Reactor模式基礎上,做如下改進:

(1)將Handler處理器的執行放入線程池,多線程進行業務處理。

(2)而對於Reactor而言,可以仍爲單個線程。如果服務器爲多核的CPU,爲充分利用系統資源,可以將Reactor拆分爲兩個線程。
下圖來源於網絡:
在這裏插入圖片描述
在這裏插入圖片描述
與單線程Reactor模式不同的是,添加了一個工作者線程池,並將 非I/O 操作從Reactor線程中移出轉交給工作者線程池來執行。這樣能夠提高Reactor線程的I/O響應,不至於因爲一些耗時的業務邏輯而延遲對後面I/O請求的處理。

注意,在上圖的改進的版本中,所有的I/O操作依舊由一個Reactor來完成,包括I/O的accept()、read()、write()以及connect()操作。

出現的問題:
當NIO線程負載過重之後,處理速度將變慢,這會導致大量客戶端連接超時,超時之後往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時,成爲系統的性能瓶頸;

多Reactor線程模式

netty中採用的就是這種模式。
在這裏插入圖片描述
Reactor線程池中的每一Reactor線程都會有自己的Selector、線程和分發的事件循環邏輯。
mainReactor可以只有一個,但subReactor一般會有多個。mainReactor線程主要負責接收客戶端的連接請求,然後將接收到的SocketChannel傳遞給subReactor,由subReactor來完成和客戶端的通信。

事件流程:

1、註冊一個Acceptor事件處理器到mainReactor中,Acceptor事件處理器所關注的事件是ACCEPT事件,
這樣mainReactor會監聽客戶端向服務器端發起的連接請求事件(ACCEPT事件)。
啓動mainReactor的事件循環。

2、客戶端向服務器端發起一個連接請求,mainReactor監聽到了該ACCEPT事件並將該ACCEPT事件
派發給Acceptor處理器來進行處理。Acceptor處理器通過accept()方法得到與這個客戶端對應的連接
(SocketChannel),然後將這個SocketChannel傳遞給subReactor線程池。

3、subReactor線程池分配一個subReactor線程給這個SocketChannel,即,將SocketChannel關注的READ事件
以及對應的READ事件處理器註冊到subReactor線程中。當然你也註冊WRITE事件以及WRITE事件處理器到
subReactor線程中以完成I/O寫操作。Reactor線程池中的每一Reactor線程都會有自己的Selector、線程
和分發的循環邏輯。

4、當有I/O事件就緒時,相關的subReactor就將事件派發給響應的處理器處理。注意,這裏subReactor線程
只負責完成I/O的read()操作,在讀取到數據後將業務邏輯的處理放入到線程池中完成,若完成業務邏輯後
需要返回數據給客戶端,則相關的I/O的write操作還是會被提交回subReactor線程來完成。

多Reactor線程模式將“接受客戶端的連接請求”和“與該客戶端的通信”分在了兩個Reactor線程來完成。mainReactor完成接收客戶端連接請求的操作,它不負責與客戶端的通信,而是將建立好的連接轉交給subReactor線程來完成與客戶端的通信,這樣一來就不會因爲read()數據量太大而導致後面的客戶端連接請求得不到即時處理的情況。並且多Reactor線程模式在海量的客戶端併發請求的情況下,還可以通過實現subReactor線程池來將海量的連接分發給多個subReactor線程,在多核的操作系統中這能大大提升應用的負載和吞吐量。

以上是關於reactor模式的演化過程。

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