Java NIO 系列文章之 淺析Reactor模式

pjmike_pj發表於2018-09-20

原文部落格地址:pjmike的部落格

前言

最近研究 Java NIO 的時候,常常看到一種 設計模式——Reactor模式,以前沒接觸過這個模式在Netty中也有應用,那麼Reactor模式是什麼?為什麼要使用Reactor模式?在NIO中如何實現Reactor模式?下面將揭開 Reactor的神祕面紗

下面的介紹參考了網上很多相關資料,所以會與網上某些資料有重複的情況,還請見諒

Reactor的介紹

Reactor 是一種和 IO 相關的設計模式,Java中的NIO中天生就對 Reactor模式提供了很好的支援,比較著名的就是 Doung Lea 大神在 《Scalable IO in Java》演示如何使用 NIO 實現Reactor模式

在維基百科上對 Reactor模式定義如下:

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

從上面的描述我們可以得到幾個關鍵點:

  • 一種事件驅動模型
  • 處理多個輸入
  • 採用多路複用將事件分發給相應的Handler處理

Reactor

netty學習系列二:NIO Reactor模型 & Netty執行緒模型這篇文章談到,Reactor實際上採用了分而治之事件驅動的思想:

分而治之: 一個連線裡完整的網路處理過程一般分為 accept,read,decode,process,encode,send這幾步。而Reactor模式將每個步驟對映為 一個Task,服務端執行緒執行的最小邏輯單元不再是一個完整的網路請求,而是 Task,且採用非阻塞方式執行。

事件驅動: 每個Task 對應特定的網路事件,當Task 準備就緒時,Reactor 收到對應的網路事件通知,並將Task 分發給繫結了對應網路事件的 Handler 執行。

總結以上幾個特點,再次說明下 Reactor模式就是 指一個或多個事件輸入同時傳遞給服務處理器(Reactor),服務處理器負責監聽各事件的狀態,當任意一個事件準備就緒時,服務處理器收到該事件通知,並將事件傳送給繫結了該對應網路事件的事件處理器(Handler)執行

Reactor模式也叫做Dispatcher模式,即 I/O多路複用統一監聽事件,收到事件後再分發(Dispatch)給相應的處理執行緒。

模式比較

談到 Reactor模式就會讓我想起觀察者模式,它倆看起來非常相似的,但是觀察者模式主要用於一對多的情況,它定義了一個一對多的依賴關係,讓多個觀察者物件監聽一個主題物件,當被觀察者狀態改變時,需要通知相應的觀察者,使這些觀察者能夠自動更新。所以實際上它們還是有不同的,觀察者模式與單個事件源關聯,而Reactor模式與多個事件源關聯

Reactor模式的三種實現

以下的說明參考了Alibaba資深技術專家李運華的極客時間專欄從0開始學架構中對於Reactor的介紹

Reactor模式有三種典型的實現方案:

  • 單Reactor單執行緒
  • 單Reactor多執行緒
  • 主從Reactor多執行緒

在介紹三個方案之前,先來了解下Reactor模式中的幾個角色:

  • Reactor: 負責響應事件,將事件分發繫結了該事件的Handler處理
  • Handler: 事件處理器,繫結了某類事件,負責執行對應事件的任務對事件進行處理
  • Acceptor:Handler的一種,繫結了 connect 事件,當客戶端發起connect請求時,Reactor會將accept事件分發給Acceptor處理

單Reactor單執行緒

reactor1

PS: 以上的select,accept,read,send是標準I/O複用模型的網路程式設計API,dispatch和"業務處理"是需要完成的操作。

方案的具體步驟如下:

  • Reactor物件通過select監控連線事件,收到事件後通過dispatch進行分發
  • 如果是連線建立的事件,則交由 Acceptor 通過accept 處理連線請求,然後建立一個 Handler 物件處理連線完成後的後續業務處理
  • 如果不是建立連線事件,則 Reactor 會分發呼叫連線對應的 Handler來響應
  • Handler 會完成 read -> 業務處理 -> send 的完整業務流程

單Reactor單執行緒的優點

  • 模型簡單,沒有多執行緒,程式通訊,競爭的問題,全部都在一個執行緒中完成

缺點

  • 只有一個程式,無法發揮多核 CPU的效能,只能採取部署多個系統來利用多核CPU,但這樣會帶來運維複雜度
  • Handler 在處理某個連線上的業務時,整個程式無法處理其他連線的事件,很容易導致效能瓶頸

單Reactor單執行緒在NIO中的實現

下圖是單Reactor單執行緒在 Java NIO中流程圖:

reactor1_nio

至於單Reactor單執行緒在 NIO 中的程式碼,詳情參閱 Doung Lea 大神的 《Scalable IO in Java》

單Reactor多執行緒

reactor2

方案步驟:

  • 主執行緒中,Reactor物件通過select 監聽連線事件,收到事件後通過 dispatch進行分發
  • 如果是連線建立的事件,則由Acceptor處理,Acceptor通過 accept接受連線,並建立一個 Handler 來處理連線後續的各種事件。
  • 如果不是連線建立事件,則Reactor會呼叫連線對應的Handler來進行相應
  • Handler 只負責響應事件,不進行業務處理,Handler 通過 read 讀取到資料後,會發給 processor 進行業務處理
  • Processor 會在獨立的子執行緒中完成真正的 業務處理,然後將響應結果發給主程式的 Handler處理,Handler 收到響應後通過 send 將響應結果返回給 client

優點:

  • 能夠充分利用多核多 CPU的處理能力

缺點

  • 多執行緒資料共享和訪問比較複雜
  • Reactor 承擔所有事件的監聽和響應,只在主執行緒中執行,瞬間高併發時會成為效能瓶頸

單Reactor多執行緒在 Java NIO中的實現

下圖是單Reactor多執行緒在 Java NIO實現的流程圖

reactor2_nio

說明:

  • 有專門一個Reactor執行緒用於監聽伺服器 ServerSocketChannel,接收客戶端的TCP連線請求
  • 網路IO的讀/寫操作等由一個 worker reactor執行緒池負責,由執行緒池中的NIO執行緒負責監聽 SocketChannel事件,進行訊息的讀取,解碼,編碼和傳送
  • 一個 NIO 執行緒可以同時處理 N條鏈路,但是一個鏈路只註冊在一個NIO 執行緒上處理,防止發生併發操作問題。

至於單Reactor多執行緒在 NIO 中的程式碼,詳情參閱 Doung Lea 大神的 《Scalable IO in Java》

多Reactor多程式/執行緒

下圖以多Reactor多程式為例

reactor3_nio

方案說明:

  • 主程式中mainReactor物件通過 select監控連線建立事件,收到事件後通過 Acceptor接收,將新的連線分配給某個子程式。
  • 子程式中的 subReactor 將 mainReactor 分配的連線加入連線佇列進行監聽,並建立一個 Handler 用於處理連線的各種事件
  • 當有新的事件發生時,subReactor 會呼叫裡連線對應的 Handler 來響應
  • Handler完成 read -> 業務處理 -> send 的完整業務流程

特點:

  • 主程式和子程式的職責非常明確,主程式只負責接收新連線,子程式負責完成後續的業務處理
  • 主程式和子程式的互動很簡單,主程式只需要把新的連線傳遞給子程式,子程式無需返回資料
  • 子程式之間是相互獨立的,無需同步共享之類的處理(這裡僅限於網路模型相關的 select,read,send等無須同步共享,"業務處理"還是有可能需要同步共享的)

多Reactor多執行緒在 Java NIO中的體現

reactor3_nio

小結

上面的總結參考了很多大神的分析,算是對 Reactor模式 有個初步的認識。

參考資料 & 鳴謝

相關文章