Reactor模型
- Reactor模型簡介
- 三類事件與三類角色
- Reactor模型整體流程
- 各種Reactor模型
- 單Reactor單線程模型
- 單Reactor多線程模型
- 主從Reactor模型
Reactor模型簡介
Reactor模型是服務器端用于處理高并發網絡IO請求的編程模型,與傳統的一請求一線程的同步式編程模型不同的是,Reactor模型是基于事件驅動的響應式編程模型,可以一個線程處理多個請求,并且是異步處理,在高并發場景下,性能大大的提升。
傳統的同步式編程模型如下:
服務端接收到請求后,從線程池中拿出一個線程(比如Tomcat里的線程池),經過Controller、Service、Dao的一頓處理,最后返回響應結果給客戶端。這種同步處理請求的方式效率并不高,可以應對一些并發量不高的場景。
基于事件驅動的響應式編程模型如下:
- 服務端啟動時,會創建Reactor反應器,然后會向Reactor注冊基本的事件類型與對應的Handler(比如網絡編程時的連接就緒事件與Acceptor)
- 當服務端接收到客戶端的請求時,服務端的Reactor就有事件就緒,Reactor會獲取到就緒的事件,在高并發場景下,Reactor有可能獲取到一批就緒的事件
- Reactor進事件分發,把事件分派給與之綁定的Handler中,Handler對事件的處理可以在當前線程中也可以在線程池中,當然在線程池中處理性能會更高
- Handler處理完畢后,如果后續還有其他處理步驟,則可以繼續注冊新的事件與對應的Handler到Reactor中;如果沒有后續,則可以返回響應結果給客戶端,返回響應結果這個動作可作為單獨的一個事件,由對應的Handler進行處理,那么也可以繼續注冊到Reactor中。
Reactor其實就是一個線程,可以看到它就在一個死循環中進行事件監聽以及事件分派,這就是事件循環。
Reactor模型底層使用了IO多路復用,通過對IO多路復用機制的合理利用,使得服務器端達到高效的處理網絡IO請求的目的。我們可以使用Java提供的NIO來實現Java版本的Reactor模型。
三類事件與三類角色
Reactor模型抽象了三類事件和三類角色,它們是Reactor模型的核心組成部分。三類事件是:連接就緒事件,讀就緒事件、寫就緒事件。三類角色是:Reactor、Acceptor、Handler。
首先我們來了解一下三類事件。
- 連接就緒事件:客戶端向服務端發起連接的時候,對應的Channel就有連接就緒事件發生。
- 讀就緒事件:當客戶端向服務器端發送數據時,對應的Channel就有讀就緒事件發生。
- 寫就緒事件:當服務器處理完客戶端發送的數據,需要返回結果時,可以向Selector注冊一個寫就緒事件并與對應的Channel關聯,那么對應Channel就有寫就緒事件發生。
我們再來了解一下三類角色。
Reactor是相當于是一個事件分派器,Reactor專門負責監聽事件的發生,并把發生的事件交給對應的處理器處理,這里的處理器指的就是Acceptor或Handler。Reactor是一個線程,它通過Selector注冊并監聽多個Channel,如果監聽到有事件發生,判斷是連接就緒事件,會交給Acceptor處理,如果發生的事件是讀就緒事件或寫就緒事件,會交給Handler處理。
如果是連接就緒事件,Reactor會將其分派給Acceptor處理,Acceptor會調用accept()方法獲取到連接對應的SocketChannel,然后會將其設置為非阻塞,注冊到Reactor的Selector中,設置關注的事件為讀就緒事件。
如果是讀就緒事件,那么Reactor會將其分派給與其綁定的Handler處理,該Handler會經過read、decode、compute、encode、send五個步驟的處理。
- read:讀取Channel中的數據
- decode:對讀到的數據進行解碼
- compute:解碼后的數據進行相應處理
- encode:對處理后的結果進行編碼
- send:編碼后的數據發送給客戶端。
之所以要進行decode和encode,是因為數據在網絡上是以二進制的形式傳輸的,因此服務端接收到后要對二進制字節碼進行解碼操作,然后才能對解碼后的數據進行處理,處理完的數據要發送到網絡,也要編碼成二進制的格式。
其中,send操作可以立刻發送數據寫出到Channel,也可以注冊一個寫就緒事件到Selector,這就相當于進行異步發送。
Reactor模型整體流程
- 首先,一開始我們只把ServerSocketChannel以及連接事件處理器Acceptor注冊到Reactor中,Reactor會把ServerSocketChannel注冊到Selector中,并設置關注連接就緒事件
- 一旦客戶端發起連接,ServerSocketChannel的連接就緒事件就緒,Reactor會將其分派給Acceptor處理,Acceptor會獲取到連接對應的SocketChannel,并將其與對應的處理器Handler注冊到Reactor,Reactor會將其注冊到Selector中。
- 隨后客戶端向服務端發送數據,Reactor監聽到對應的SocketChannel有讀就緒事件發生,會將其分派給與其綁定的Handler進行處理。
- Handler被分派到讀就緒事件時,會經過read、decode、compute、encode、send五個步驟的處理。
- 服務端要把處理結果返回給客戶端,會向Reactor注冊一個對應Channel的寫就緒事件,Reactor監聽到寫就緒事件,會分派給與該Channel綁定的Handler處理,Handler把返回結果發送出去。
各種Reactor模型
Reactor編程模型不是只有單一的一種編程模型,它有單Reactor單線程模型、單Reactor多線程模型、主從Reactor模型等多種模型。
單Reactor單線程模型
單Reactor單線程模型是最簡單的模型,當然性能也是最低的。單Reactor模型只有一個Reactor,這個Reactor即負責處理連接建立,也負責數據讀寫和計算等處理。并且由于是單線程模型,所以數據讀取后的計算處理,也是在當前線程中完成。
這種單Reactor單線程模型雖然性能比其他的Reactor模型低,但是優點是比較的簡單,沒有了多線程就意味著沒有了“多線程翻車”的一些情況出現。值得一提的是,Redis的事件驅動框架就是單Reactor單線程模型。
但是由于連接建立與IO處理都在一個Reactor線程中進行,因此在高并發場景下,該Reactor線程的壓力會非常大,特別是在數據計算還比較復雜的場景,單線程的Reactor模型有可能支撐不住。之所以Redis使用單Reactor單線程模型還能支撐高并發讀寫的場景,其中一個原因是因為Redis內部不涉及太復雜的數據計算。
單Reactor多線程模型
單Reactor多線程模型是在單Reactor模型的基礎上增加了線程池,可以把decode、compute、encode等需要計算的操作提交到線程池執行,而Reactor線程則專注于連接建立與IO的處理(也就是read和send)。
把涉及到計算的處理抽到了線程池中處理,可以在一定程度上緩解Reactor線程的壓力。但是由于還是單Reactor模型,IO處理依然在該Reactor線程中進行,高并發場景下,可能會由于大量的IO請求需要處理而導致連接建立的處理受到影響,該Reactor線程可能無法及時響應客戶端連接建立的請求。
主從Reactor模型
主從Reactor模型在單Reactor多線程模型基礎上做了改進,把IO處理分離到了子Reactor中,主Reactor只專注于連接請求的處理,主Reactor一般只有一個(也可有多個),而子Reactor一般有多個。當主Reactor上有連接就緒事件發生時,通過Acceptor獲取到連接對應的SocketChannel,然后把該SocketChannel注冊到子Reactor中,由子Reactor監聽該Channel的讀就緒事件并處理。
由于連接建立與IO處理分離到了不同的Reactor,當有大量并發的IO請求需要處理時,也不會影響到客戶端建立連接的請求。