1. Reactor 模式
Reactor模式
也叫 Dispatcher模式
,其基於事件驅動處理模式,基本設計思想是I/O 複用結合線程池。下圖爲事件驅動處理示意圖,可以看到大致流程爲服務端程序監聽處理多路請求傳入的事件,並將事件分派給請求對應的處理線程完成處理
Reactor 模式中有 2 個關鍵組件:
Reactor
運行在一個單獨的線程中,負責監聽和分發事件,分發事件給適當的 Handler 來對 IO 請求做出反應Handler
將自身與事件綁定,實際執行 I/O 要完成的事件。Reactor 通過調度適當的 Handler 來響應 I/O 事件,執行非阻塞操作
根據 Reactor 的數量和處理事件的線程數量的不同,Reactor 模式有 3 種實現:
- 單 Reactor 單線程
- 單 Reactor 多線程
- 主從 Reactor 多線程
1.1 單 Reactor 單線程
實現的示意圖如下,其消息的處理流程可概括爲以下幾步:
- Reactor 通過 select 監控連接上的所有事件,收到事件後通過 dispatch 將其分發
- 如果該事件是請求連接建立,則由 Acceptor 接受連接,併爲其創建 Handler 處理後續事件
- 假如不是建立連接事件,Reactor 將其分發給對應的 Handler 來響應
- Handler 實際處理事件,完成 read->handle->send 的業務處理流程
單Reactor單線程模型
只是進行了代碼組件的區分,整體操作還是單線程,其優缺點如下:
優點
模型簡單,所有處理都在一個線程中完成,沒有多線程上下文切換的開銷,沒有進程通信及鎖競爭的問題缺點
- 只有一個線程,無法完全發揮多核 CPU 的性能,造成浪費
- Handler 在處理某個連接上的業務時,整個進程無法處理其他連接事件,容易導致性能瓶頸
- 一旦 Reactor 線程意外中斷或者跑飛,可能導致整個系統通信模塊不可用,無法接收和處理外部消息,造成節點故障
適用場景
客戶端數量有限,業務處理非常快速,例如 Redis 就是使用單 Reactor 單線程模型
1.2 單 Reactor 多線程
實現的示意圖如下所示,消息處理流程可以分爲以下幾步:
- Reactor 通過 select 監控連接上的所有事件,收到事件後通過 dispatch 將其分發
- 如果是請求建立連接的事件,則由 Acceptor 通過 accept 處理連接請求,然後創建一個 Handler 處理該連接,完成後續的讀寫事件
- 假如不是建立連接事件,Reactor 將其分發給對應的 Handler 來響應。該實現中 Handler 只負責響應事件,read 讀取數據後會將其分發給 Worker 線程池進行 handle 業務處理
- Worker 線程池調度線程完成實際的業務處理,並將響應結果返回給主線程 Handler,Handler 通過 send 將結果返回給請求端,完成請求響應的流程
單 Reactor 多線程模型
相對於單Reactor單線程模型
來說,handle 業務邏輯交由線程池來處理,其優缺點如下:
優點
可以充分利用多核 CPU 的處理能力,使 Reactor 更專注於事件分發工作,提升整個應用的吞吐缺點
- 多線程環境下數據共享和訪問比較複雜,子線程完成業務處理後把結果傳遞給主線程 Handler 進行發送,需要考慮共享數據的互斥和保護機制
- Reactor 主線程單線程運行,承擔所有事件的監聽和響應,高併發場景下會成爲性能瓶頸
1.3 主從 Reactor 多線程
單 Reactor 多線程模型
中 Reactor 在單線程中運行,高併發場景下容易成爲性能瓶頸,針對這個缺點一個解決方案是讓 Reactor 在多線程中運行,於是產生了主從 Reactor 多線程模型
。相比第二種模型,它將 Reactor 分成兩部分:
- MainReactor 只用來處理網絡IO連接建立的操作,並將建立的連接指定註冊到 SubReactor 上
- SubReactor 負責處理註冊其上的連接的事件,完成業務處理,通常 SubReactor 個數可與CPU個數等同
主從 Reactor 多線程模型
消息處理流程可以分爲以下幾個步驟:
- Reactor 主線程 MainReactor 通過 select 監控建立連接的事件,收到事件後通過 Acceptor 接收,處理建立連接事件
- Acceptor 建立連接後,MainReactor 將連接分配給 Reactor 子線程 SubReactor 進行處理。SubReactor 會將該連接加入連接隊列進行監聽,並創建一個 Handler 用於處理該連接上的讀寫事件
- 當有讀寫事件發生時,SubReactor 調用連接對應的 Handler 進行響應,Handler 讀取數據後將其分發給 Worker 線程池進行 handle 業務處理
- Worker 線程池調度線程完成實際的業務處理,並將響應結果返回給 SubReactor 的 Handler,Handler 通過 send 將結果返回給請求端,完成請求響應的流程
這種 Reactor 實現模型使用非常廣泛,比較著名的包括 Nginx 主從 Reactor 多進程模型,Memcached 主從多線程,Netty 主從多線程模型的支持,其主要優點如下:
- MainReactor 與 SubReactor 的數據交互簡單,MainReactor 只需把新連接交給 SubReactor,SubReactor 無需返回數據
- MainReactor 與 SubReactor 主從職責明確,MainReactor只負責接收新連接,SubReactor 負責完成後續的業務處理
2. Proactor 模式
在 Reactor 模式中,Reactor 在用戶進程通過 select 輪詢等待某個事件的發生,然後將這個事件分發給 Handler,由 Handler 來做實際的讀寫操作。這個過程中讀寫操作依然是同步的,如果把 I/O 操作改爲異步,即交給操作系統來完成,就能進一步提升性能,這就是異步網絡模型 Proactor
Proactor模式
也是基於事件驅動模型,不過 Proactor 不關注讀取就緒事件
,而是關注讀取完成事件
,因爲異步IO都是操作系統將數據讀寫到指定的緩衝區,應用程序直接從緩衝區取用即可。以下爲示意圖,其各個模塊組成如下:
Procator Initiator
負責創建 Procator 和 Handler,並將 Procator 和 Handler 註冊到內核Asynchronous Operation Processor
負責處理註冊請求,並進行 IO 操作,IO 操作完成後會通知 ProcatorProcator
Procator 在內核進程等待 IO 完成的事件,根據事件類型回調對應的 Handler 進行業務處理。Handler 負責完成實際的業務處理,也能註冊新的 Handler 到內核
以讀取操作爲例,Proactor模式
的消息處理流程如下:
- 應用程序初始化 Proactor 和相應的 Handler,然後將其註冊到內核
- 請求端發送數據,操作系統調用內核線程完成讀取操作,並將讀取的內容放入指定的緩衝區
- Proactor 在內核進程中等待讀取操作完成事件,捕獲到讀取完成事件後,回調相應的 Handler
- Handler 直接從緩衝區讀取數據處理,已經不需要進行實際的讀取操作
綜上可以明白 Proactor 與 Reactor 的區別:
- Reactor 由應用程序自行輪詢監聽事件,Proactor 則由內核進程監聽事件,開銷更小
- Reactor 讀寫的 IO 操作由應用程序自行完成處理,Proactor 是由內核異步 IO 完成讀寫操作,再發出通知
理論上 Proactor 比 Reactor 效率更高,但是 Proactor 有如下缺點:
內存使用
緩衝區在讀寫操作的時間段內必須持續佔用內存,並且每個併發操作都要求有獨立的緩衝區,相比 Reactor 模式在準備好讀寫前不要求開闢緩存,Proactor 對內存提出了更多的要求操作系統支持
異步 I/O 依賴操作系統支持,Windows 中通過 IOCP 實現了真正的異步 I/O,而在 Linux 系統中異步 I/O 目前還不完善