1. 程式人生 > >Reactor模式詳解

Reactor模式詳解

什麼是Reactor模式

要回答這個問題,首先當然是求助Google或Wikipedia,其中Wikipedia上說:“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模式首先是事件驅動的,有一個或多個併發輸入源,有一個Service Handler,有多個Request Handlers

;這個Service Handler會同步的將輸入的請求(Event)多路複用的分發給相應的Request Handler。如果用圖來表達: 從結構上,這有點類似生產者消費者模式,即有一個或多個生產者將事件放入一個Queue中,而一個或多個消費者主動的從這個Queue中Poll事件來處理;而Reactor模式則並沒有Queue來做緩衝,每當一個Event輸入到Service Handler之後,該Service Handler會主動的根據不同的Event型別將其分發給對應的Request Handler來處理。 更學術的,這篇文章(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
)上說:“The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier
”。這段描述和Wikipedia上的描述類似,有多個輸入源,有多個不同的EventHandler(RequestHandler)來處理不同的請求,Initiation Dispatcher用於管理EventHander,EventHandler首先要註冊到Initiation Dispatcher中,然後Initiation Dispatcher根據輸入的Event分發給註冊的EventHandler;然而Initiation Dispatcher並不監聽Event的到來,這個工作交給Synchronous Event Demultiplexer來處理。

Reactor模式結構

在解決了什麼是Reactor模式後,我們來看看Reactor模式是由什麼模組構成。圖是一種比較簡潔形象的表現方式,因而先上一張圖來表達各個模組的名稱和他們之間的關係:Handle:即作業系統中的控制代碼,是對資源在作業系統層面上的一種抽象,它可以是開啟的檔案、一個連線(Socket)、Timer等。由於Reactor模式一般使用在網路程式設計中,因而這裡一般指Socket Handle,即一個網路連線(Connection,在Java NIO中的Channel)。這個Channel註冊到Synchronous Event Demultiplexer中,以監聽Handle中發生的事件,對ServerSocketChannnel可以是CONNECT事件,對SocketChannel可以是READ、WRITE、CLOSE事件等。Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到來,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的執行返回的事件型別。這個模組一般使用作業系統的select來實現。在Java NIO中用Selector來封裝,當Selector.select()返回時,可以呼叫Selector的selectedKeys()方法獲取Set<SelectionKey>,一個SelectionKey表達一個有事件發生的Channel以及該Channel上的事件型別。上圖的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程如果是對的,那內部實現應該是select()方法在事件到來後會先設定Handle的狀態,然後返回。不瞭解內部實現機制,因而保留原圖。Initiation Dispatcher:用於管理Event Handler,即EventHandler的容器,用以註冊、移除EventHandler等;另外,它還作為Reactor模式的入口呼叫Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,當阻塞等待返回時,根據事件發生的Handle將其分發給對應的Event Handler處理,即回撥EventHandler中的handle_event()方法。Event Handler:定義事件處理方法:handle_event(),以供InitiationDispatcher回撥使用。Concrete Event Handler:事件EventHandler介面,實現特定事件處理邏輯。

Reactor模式模組之間的互動

簡單描述一下Reactor各個模組之間的互動流程,先從序列圖開始: 1. 初始化InitiationDispatcher,並初始化一個Handle到EventHandler的Map。 2. 註冊EventHandler到InitiationDispatcher中,每個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 An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server來分析Reactor模式,這個Logging Server的實現完全遵循這裡對Reactor描述,因而放在這裡以做參考。Logging Server中的Reactor模式實現分兩個部分:Client連線到Logging Server和Client向Logging Server寫Log。因而對它的描述分成這兩個步驟。Client連線到Logging Server 1. Logging Server註冊LoggingAcceptor到InitiationDispatcher。 2. Logging Server呼叫InitiationDispatcher的handle_events()方法啟動。 3. InitiationDispatcher內部呼叫select()方法(Synchronous Event Demultiplexer),阻塞等待Client連線。 4. Client連線到Logging Server。 5. InitiationDisptcher中的select()方法返回,並通知LoggingAcceptor有新的連線到來。  6. LoggingAcceptor呼叫accept方法accept這個新連線。 7. LoggingAcceptor建立新的LoggingHandler。 8. 新的LoggingHandler註冊到InitiationDispatcher中(同時也註冊到Synchonous Event Demultiplexer中),等待Client發起寫log請求。Client向Logging Server寫Log 1. Client傳送log到Logging server。 2. InitiationDispatcher監測到相應的Handle中有事件發生,返回阻塞等待,根據返回的Handle找到LoggingHandler,並回調LoggingHandler中的handle_event()方法。 3. LoggingHandler中的handle_event()方法中讀取Handle中的log資訊。 4. 將接收到的log寫入到日誌檔案、資料庫等裝置中。 3.4步驟迴圈直到當前日誌處理完成。 5. 返回到InitiationDispatcher等待下一次日誌寫請求。 在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有對Reactor模式的C++的實現版本,多年不用C++,因而略過。 

Java NIO對Reactor的實現

在Java的NIO中,對Reactor模式有無縫的支援,即使用Selector類封裝了作業系統提供的Synchronous Event Demultiplexer功能。這個Doug Lea已經在Scalable IO In Java中有非常深入的解釋了,因而不再贅述,另外這篇文章對Doug Lea的Scalable IO In Java有一些簡單解釋,至少它的程式碼格式比Doug Lea的PPT要整潔一些。 需要指出的是,不同這裡使用InitiationDispatcher來管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment來儲存對應的EventHandler,因而不需要註冊EventHandler這個步驟,或者設定Attachment就是這裡的註冊。而且在這篇文章中,Doug Lea從單執行緒的Reactor、Acceptor、Handler實現這個模式出發;演化為將Handler中的處理邏輯多執行緒化,實現類似Proactor模式,此時所有的IO操作還是單執行緒的,因而再演化出一個Main Reactor來處理CONNECT事件(Acceptor),而多個Sub Reactor來處理READ、WRITE等事件(Handler),這些Sub Reactor可以分別再自己的執行緒中執行,從而IO操作也多執行緒化。這個最後一個模型正是Netty中使用的模型。並且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events的9.5 Determine the Number of Initiation Dispatchers in an Application中也有相應的描述。

EventHandler介面定義

對EventHandler的定義有兩種設計思路:single-method設計和multi-method設計:A single-method interface:它將Event封裝成一個Event Object,EventHandler只定義一個handle_event(Event event)方法。這種設計的好處是有利於擴充套件,可以後來方便的新增新的Event型別,然而在子類的實現中,需要判斷不同的Event型別而再次擴充套件成 不同的處理方法,從這個角度上來說,它又不利於擴充套件。另外在Netty3的使用過程中,由於它不停的建立ChannelEvent類,因而會引起GC的不穩定。A multi-method interface:這種設計是將不同的Event型別在 EventHandler中定義相應的方法。這種設計就是Netty4中使用的策略,其中一個目的是避免ChannelEvent建立引起的GC不穩定, 另外一個好處是它可以避免在EventHandler實現時判斷不同的Event型別而有不同的實現,然而這種設計會給擴充套件新的Event型別時帶來非常 大的麻煩,因為它需要該介面。

Reactor模式的缺點

Reactor模式的缺點貌似也是顯而易見的: 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模式。