Reactor 典型的 NIO 編程模型
Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 編程模型的思想,大部分 NIO 框架和一些中間件的 NIO 編程都與它一樣或是它的變體。本文結合 PPT 按照自己的理解整理而來,最終編寫了一個簡單的 NIO 回顯服務。
Reactor 之所以高效是因為采用了分而治之和事件驅動的設計。大部分網絡服務像 Web 服務器、分布式對象的通信等大多數具有相同的基本處理流程:
- 讀取請求數據 - read
- 按協議解析請求 - decode
- 業務處理 - process
- 按協議編碼內容生成響應 - encode
- 發送響應 - send
傳統的服務器設計是:一連接一處理線程,也就是我們常說的 BIO 編程。BIO 在讀取和發送時是阻塞的,在請求的整個生命周期內,不管有沒有數據可讀或待發送,都綁定和占用
傳統模型中,線程的處理單元是一次完整的請求,為了把線程解放出來,Reactor 對這個處理單元進行了分解。
1. 分而治之
Reactor 模式將處理過程分為多個小任務,每個任務執行一個非阻塞的操作,任務通常由一個 IO 事件觸發執行。這種機制在 java.nio 中提供了支持:
- 非阻塞的讀和寫
- 調度與發生的 IO 事件關聯的任務
BIO 線程是以 read->decode->process->encode->send 的順序串行處理,NIO 將其分成了三個執行單元:讀取、業務處理和發送,處理過程如下:
- 讀取:如果無數據可讀,線程返回線程池;發生讀IO事件
- 業務處理:線程同步處理完業務後,生成響應內容並編碼,返回線程池
- 發送:發生寫IO事件,申請一個線程進行發送
可以看出一個明顯的區別就是,一次請求的處理過程是由多個不同的線程完成的,感覺和指令的串行執行和並行執行有點類似。
分而治之的關鍵在於非阻塞,這樣就能充分利用線程,壓榨 CPU,提高系統的吞吐能力。
2. 事件驅動
通常比其他模型更高效,它使用的資源更少,不用針對每個請求啟用一條線程,減少了上下文切換,減少阻塞。但任務調度可能會慢,必須手動將事件和處理動作綁定。
通常編程比較困難,它必須為服務設計多個邏輯狀態,以便跟蹤和中斷恢復,這也是在非阻塞編程中有大量狀態機運用的原因。
NIO 中總共設計了 4 種事件,每個事件發生都會調度關聯的任務,分別是:
- OP_ACCEPT: 服務端監聽到了一個連接,準備接收
- OP_CONNECT: 客戶端與服務器連接建立成功
- OP_READ: 讀事件就緒,通道有數據可讀
- OP_WRITE: 寫事件就緒,可以向通道寫入數據
java.nio 對事件驅動也提供了支持:
- Channels: 連接到文件、Socket 等,支持非阻塞讀取
- Buffers: 類似數組的對象,可由通道直接讀取或寫入
- Selectors: 通知哪組通道有 IO 事件
- SelectionKeys: 維護 IO 事件的狀態和綁定信息
3. Reactor 模式
Reactor 是一種設計模式,wikipedia 對其定義如下:
Reactor 是一個或多個輸入事件的處理模式,用於處理並發傳遞給服務處理程序的服務請求。服務處理程序判斷傳入請求發生的事件,並將它們同步的分派給關聯的請求處理程序。
Reactor 模式按照職責不同,通常可以把線程分為 Reactor 線程、IO 線程和業務線程:
- Reactor 線程:輪詢通知發生IO的通道,並分派合適的 Handler 處理
- IO 線程:執行實際的讀寫操作
- 業務線程:執行應用程序的業務邏輯
PPT 中介紹了三種版本的實現。
3.1 單線程版本
單線程版本就是用一個線程完成事件的通知、實際的 I/O 操作和業務處理。Reactor 作用就是要迅速的觸發 Handler ,顯然 Handler 處理的過程會導致 Reactor 變慢,此時可以將 非 IO 操作從 Reactor 線程中分離。
3.2 多線程版本
多線程版本將業務處理和 I/O 操作進行分離,Reactor 線程只關註事件分發和實際的 IO 操作,業務處理如協議的編解碼都分配給線程池處理。可能會有這樣的情況發生,業務處理很快,大部分的 Reactor 線程都在處理 IO,導致 CPU 閑置,降低了響應速度。
3.3 主從 Reactor 版本
主從 Reactor 版本設計了一個 主Reactor 用於處理連接接收事件,多個 從Reactor 處理實際的 I/O,分工合作,匹配 CPU 和 IO 速率。
3.4 一個變體
以上的三個版本中,Reactor 線程都參與了 IO 操作,有一種變體是把實際的 IO 操作也轉移到線程池線程中,這樣從數據的讀取到業務的處理都是由一個線程完成,可以避免鎖的競爭。
小結
看的再多都不如手動寫一個,哪怕找個例子抄一遍也行。這裏對 PPT 中介紹的3個版本實現了一個簡單的回顯服務,內部有比較詳細的代碼註釋。
源碼地址:https://github.com/tonwu/reactor
Reactor 典型的 NIO 編程模型