Reactor 與 Proactor 線程模型

1. Reactor 模式

Reactor模式也叫 Dispatcher模式,其基於事件驅動處理模式,基本設計思想是I/O 複用結合線程池。下圖爲事件驅動處理示意圖,可以看到大致流程爲服務端程序監聽處理多路請求傳入的事件,並將事件分派給請求對應的處理線程完成處理

在這裏插入圖片描述

Reactor 模式中有 2 個關鍵組件:

  1. Reactor
    運行在一個單獨的線程中,負責監聽和分發事件,分發事件給適當的 Handler 來對 IO 請求做出反應
  2. Handler
    將自身與事件綁定,實際執行 I/O 要完成的事件。Reactor 通過調度適當的 Handler 來響應 I/O 事件,執行非阻塞操作

根據 Reactor 的數量和處理事件的線程數量的不同,Reactor 模式有 3 種實現

  1. 單 Reactor 單線程
  2. 單 Reactor 多線程
  3. 主從 Reactor 多線程

1.1 單 Reactor 單線程

實現的示意圖如下,其消息的處理流程可概括爲以下幾步:

  1. Reactor 通過 select 監控連接上的所有事件,收到事件後通過 dispatch 將其分發
  2. 如果該事件是請求連接建立,則由 Acceptor 接受連接,併爲其創建 Handler 處理後續事件
  3. 假如不是建立連接事件,Reactor 將其分發給對應的 Handler 來響應
  4. Handler 實際處理事件,完成 read->handle->send 的業務處理流程

在這裏插入圖片描述

單Reactor單線程模型只是進行了代碼組件的區分,整體操作還是單線程,其優缺點如下:

  • 優點
    模型簡單,所有處理都在一個線程中完成,沒有多線程上下文切換的開銷,沒有進程通信及鎖競爭的問題
  • 缺點
  1. 只有一個線程,無法完全發揮多核 CPU 的性能,造成浪費
  2. Handler 在處理某個連接上的業務時,整個進程無法處理其他連接事件,容易導致性能瓶頸
  3. 一旦 Reactor 線程意外中斷或者跑飛,可能導致整個系統通信模塊不可用,無法接收和處理外部消息,造成節點故障
  • 適用場景
    客戶端數量有限,業務處理非常快速,例如 Redis 就是使用單 Reactor 單線程模型

1.2 單 Reactor 多線程

實現的示意圖如下所示,消息處理流程可以分爲以下幾步:

  1. Reactor 通過 select 監控連接上的所有事件,收到事件後通過 dispatch 將其分發
  2. 如果是請求建立連接的事件,則由 Acceptor 通過 accept 處理連接請求,然後創建一個 Handler 處理該連接,完成後續的讀寫事件
  3. 假如不是建立連接事件,Reactor 將其分發給對應的 Handler 來響應。該實現中 Handler 只負責響應事件,read 讀取數據後會將其分發給 Worker 線程池進行 handle 業務處理
  4. Worker 線程池調度線程完成實際的業務處理,並將響應結果返回給主線程 Handler,Handler 通過 send 將結果返回給請求端,完成請求響應的流程

在這裏插入圖片描述
單 Reactor 多線程模型 相對於單Reactor單線程模型來說,handle 業務邏輯交由線程池來處理,其優缺點如下:

  • 優點
    可以充分利用多核 CPU 的處理能力,使 Reactor 更專注於事件分發工作,提升整個應用的吞吐
  • 缺點
  1. 多線程環境下數據共享和訪問比較複雜,子線程完成業務處理後把結果傳遞給主線程 Handler 進行發送,需要考慮共享數據的互斥和保護機制
  2. Reactor 主線程單線程運行,承擔所有事件的監聽和響應,高併發場景下會成爲性能瓶頸

1.3 主從 Reactor 多線程

單 Reactor 多線程模型中 Reactor 在單線程中運行,高併發場景下容易成爲性能瓶頸,針對這個缺點一個解決方案是讓 Reactor 在多線程中運行,於是產生了主從 Reactor 多線程模型。相比第二種模型,它將 Reactor 分成兩部分:

  1. MainReactor 只用來處理網絡IO連接建立的操作,並將建立的連接指定註冊到 SubReactor 上
  2. SubReactor 負責處理註冊其上的連接的事件,完成業務處理,通常 SubReactor 個數可與CPU個數等同

主從 Reactor 多線程模型消息處理流程可以分爲以下幾個步驟:

  1. Reactor 主線程 MainReactor 通過 select 監控建立連接的事件,收到事件後通過 Acceptor 接收,處理建立連接事件
  2. Acceptor 建立連接後,MainReactor 將連接分配給 Reactor 子線程 SubReactor 進行處理。SubReactor 會將該連接加入連接隊列進行監聽,並創建一個 Handler 用於處理該連接上的讀寫事件
  3. 當有讀寫事件發生時,SubReactor 調用連接對應的 Handler 進行響應,Handler 讀取數據後將其分發給 Worker 線程池進行 handle 業務處理
  4. Worker 線程池調度線程完成實際的業務處理,並將響應結果返回給 SubReactor 的 Handler,Handler 通過 send 將結果返回給請求端,完成請求響應的流程

在這裏插入圖片描述
這種 Reactor 實現模型使用非常廣泛,比較著名的包括 Nginx 主從 Reactor 多進程模型,Memcached 主從多線程,Netty 主從多線程模型的支持,其主要優點如下:

  1. MainReactor 與 SubReactor 的數據交互簡單,MainReactor 只需把新連接交給 SubReactor,SubReactor 無需返回數據
  2. MainReactor 與 SubReactor 主從職責明確,MainReactor只負責接收新連接,SubReactor 負責完成後續的業務處理

2. Proactor 模式

在 Reactor 模式中,Reactor 在用戶進程通過 select 輪詢等待某個事件的發生,然後將這個事件分發給 Handler,由 Handler 來做實際的讀寫操作。這個過程中讀寫操作依然是同步的,如果把 I/O 操作改爲異步,即交給操作系統來完成,就能進一步提升性能,這就是異步網絡模型 Proactor

Proactor模式也是基於事件驅動模型,不過 Proactor 不關注讀取就緒事件,而是關注讀取完成事件,因爲異步IO都是操作系統將數據讀寫到指定的緩衝區,應用程序直接從緩衝區取用即可。以下爲示意圖,其各個模塊組成如下:

  1. Procator Initiator
    負責創建 Procator 和 Handler,並將 Procator 和 Handler 註冊到內核
  2. Asynchronous Operation Processor
    負責處理註冊請求,並進行 IO 操作,IO 操作完成後會通知 Procator
  3. Procator
    Procator 在內核進程等待 IO 完成的事件,根據事件類型回調對應的 Handler 進行業務處理。Handler 負責完成實際的業務處理,也能註冊新的 Handler 到內核

在這裏插入圖片描述
以讀取操作爲例,Proactor模式的消息處理流程如下:

  1. 應用程序初始化 Proactor 和相應的 Handler,然後將其註冊到內核
  2. 請求端發送數據,操作系統調用內核線程完成讀取操作,並將讀取的內容放入指定的緩衝區
  3. Proactor 在內核進程中等待讀取操作完成事件,捕獲到讀取完成事件後,回調相應的 Handler
  4. Handler 直接從緩衝區讀取數據處理,已經不需要進行實際的讀取操作

綜上可以明白 Proactor 與 Reactor 的區別:

  1. Reactor 由應用程序自行輪詢監聽事件,Proactor 則由內核進程監聽事件,開銷更小
  2. Reactor 讀寫的 IO 操作由應用程序自行完成處理,Proactor 是由內核異步 IO 完成讀寫操作,再發出通知

理論上 Proactor 比 Reactor 效率更高,但是 Proactor 有如下缺點:

  1. 內存使用
    緩衝區在讀寫操作的時間段內必須持續佔用內存,並且每個併發操作都要求有獨立的緩衝區,相比 Reactor 模式在準備好讀寫前不要求開闢緩存,Proactor 對內存提出了更多的要求
  2. 操作系統支持
    異步 I/O 依賴操作系統支持,Windows 中通過 IOCP 實現了真正的異步 I/O,而在 Linux 系統中異步 I/O 目前還不完善
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章