Reactor 模式

wavesZh發表於2018-10-10

Reactor

什麼是 Reactor 模式

wiki:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”

為什麼會有 Reactor 呢

對於應用程式而言,CPU 的處理速度是遠遠快於 IO 的速度的。如果CPU為了IO操作(例如從Socket讀取一段資料)而阻塞顯然是不划算的。好一點的方法是分為多程式或者執行緒去進行處理,但是這樣會帶來一些程式切換的開銷,試想一個程式一個資料讀了500ms,期間程式切換到它3次,但是CPU卻什麼都不能幹,就這麼切換走了,是不是也不划算?

這時先驅們找到了事件驅動,或者叫回撥的方式,來完成這件事情。這種方式就是,應用業務向一箇中間人註冊一個回撥(event handler),當IO就緒後,就這個中間人產生一個事件,並通知此handler進行處理。這種回撥的方式,也體現了“好萊塢原則”(Hollywood principle)-“Don’t call us, we’ll call you”,在我們熟悉的IoC中也有用到。看來軟體開發真是互通的!

Reactor 應用場景

Reactor 核心是解決多請求問題。一般來說,Thread-Per-Connection 的應用場景併發量不是特別大,如果併發量過大,會導致執行緒資源瞬間耗盡,導致服務陷入阻塞,這個時候就需要 Reactor 模式來解決這個問題。Reactor 通過多路複用的思想大大減少執行緒資源的使用。

Reactor 結構

結構

上圖是 Reactor 模型,主要涉及的類:

  • Initiation Dispatcher:EventHandler 的容器,用來註冊、移除 EventHandler 等;另外,它作為 Reactor 模式的入口呼叫 Synchronous Event Demultiplexer 的 select 方法以阻塞等待事件的返回,當阻塞事件返回時,將事件發生的 Handle 分發到相應的 EvenHandler 處理。
  • Even Handler:定義了事件處理的方法。
  • Handle:即作業系統中的控制程式碼,是對資源在作業系統層面上的一種抽象,它可以是開啟的檔案、一個連線(Socket)、Timer等。
  • Synchronous Event Demultiplexer:使用一個事件迴圈 ,以阻止所有的資源。當可以啟動一個同步操作上的資源不會阻塞,多路分解器傳送資源到分發器。

Reactor 時序圖

時序圖

  1. 初始化 InitationDispatcher,並初始化一個Handle到EventHandler的Map。
  2. 註冊 EvenHandler 到 InitationDispatcher,每個 EventHandler 包含對相應 Handle 的引用,從而建立Handle到EventHandler的對映(Map)。
  3. 呼叫 InitiationDispatcher 的 handle_events() 方法以啟動 Event Loop。在 Event Loop 中,呼叫 select()方法(Synchronous Event Demultiplexer)阻塞等待Event發生。
  4. 當某個或某些 Handle 的 Event 發生後,select() 方法返回,InitiationDispatcher 根據返回的Handle找到註冊的 EventHandler ,並回撥該 EventHandler 的 handle_events() 方法。
  5. 在 EventHandler 的 handle_events() 方法中還可以向 InitiationDispatcher 中註冊新的 Eventhandler,比如對 AcceptorEventHandler 來說,當有新的 client 連線時,它會產生新的 EventHandler 以處理新的連線,並註冊到 InitiationDispatcher 中。

Reactor 模式

單執行緒 Reactor 模式

reactor單執行緒

簡單來說,接收請求和處理請求是同一執行緒中處理。

對於一些小容量應用場景,可以使用單執行緒模型。但是對於高負載、大併發或大資料量的應用場景卻不合適,主要原因如下: ① 一個NIO執行緒同時處理成百上千的鏈路,效能上無法支撐,即便NIO執行緒的CPU負荷達到100%,也無法滿足海量訊息的讀取和傳送; ② 當NIO執行緒負載過重之後,處理速度將變慢,這會導致大量客戶端連線超時,超時之後往往會進行重發,這更加重了NIO執行緒的負載,最終會導致大量訊息積壓和處理超時,成為系統的效能瓶頸;

多執行緒 Reactor 模式

reactor多執行緒

簡單來說,接收請求和處理請求是不同執行緒中處理。

mainReactor 一般只有一個,主要負責接收客戶端的連線並將其傳遞給 subReactor。subReactor 一般會有多個,主要負責處理與客戶端的通訊。

注意:上圖使用了Thread Pool來處理耗時的業務邏輯,提高Reactor執行緒的I/O響應,不至於因為一些耗時的業務邏輯而延遲對後面I/O請求的處理。

Reactor 的優缺點

優點:

  1. 大多數設計模式的共性:解耦、提升複用性、模組化、可移植性、事件驅動、細力度的併發控制等。
  2. 更為顯著的是對效能的提升,即不需要每個 Client 對應一個執行緒,減少執行緒的使用。

缺點:

  1. 相比傳統的簡單模型,Reactor增加了一定的複雜性,因而有一定的門檻,並且不易於除錯。
  2. Reactor模式需要底層的Synchronous Event Demultiplexer支援,比如Java中的Selector支援,作業系統的select系統呼叫支援,如果要自己實現Synchronous Event Demultiplexer可能不會有那麼高效。
  3. Reactor模式在IO讀寫資料時還是在同一個執行緒中實現的,即使使用多個Reactor機制的情況下,那些共享一個Reactor的Channel如果出現一個長時間的資料讀寫,會影響這個Reactor中其他Channel的相應時間,比如在大檔案傳輸時,IO操作就會影響其他Client的相應時間,因而對這種操作,使用傳統的Thread-Per-Connection或許是一個更好的選擇,或則此時使用Proactor模式。

Reactor 程式碼實現

預告

介紹與 Reactor 相關的 NIO 以及 Netty。

參考文獻

相關文章