前記
第一次聽到Reactor模式是三年前的某個(gè)晚上,一個(gè)室友突然跑過來問我什么是Reactor模式?我上網(wǎng)查了一下,很多人都是給出NIO中的 Selector的例子,而且就是NIO里Selector多路復(fù)用模型,只是給它起了一個(gè)比較fancy的名字而已,雖然它引入了EventLoop概 念,這對(duì)我來說是新的概念,但是代碼實(shí)現(xiàn)卻是一樣的,因而我并沒有很在意這個(gè)模式。然而最近開始讀Netty源碼,而Reactor模式是很多介紹Netty的文章中被大肆宣傳的模式,因而我再次問自己,什么是Reactor模式?本文就是對(duì)這個(gè)問題關(guān)于我的一些理解和嘗試著來解答。什么是Reactor模式
要回答這個(gè)問題,首先當(dāng)然是求助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.”。從這個(gè)描述中,我們知道Reactor模式首先是事件驅(qū)動(dòng)的,有一個(gè)或多個(gè)并發(fā)輸入源,有一個(gè)Service Handler,有多個(gè)Request Handlers;這個(gè)Service Handler會(huì)同步的將輸入的請(qǐng)求(Event)多路復(fù)用的分發(fā)給相應(yīng)的Request Handler。如果用圖來表達(dá):
從結(jié)構(gòu)上,這有點(diǎn)類似生產(chǎn)者消費(fèi)者模式,即有一個(gè)或多個(gè)生產(chǎn)者將事件放入一個(gè)Queue中,而一個(gè)或多個(gè)消費(fèi)者主動(dòng)的從這個(gè)Queue中Poll事件來處理;而Reactor模式則并沒有Queue來做緩沖,每當(dāng)一個(gè)Event輸入到Service Handler之后,該Service Handler會(huì)主動(dòng)的根據(jù)不同的Event類型將其分發(fā)給對(duì)應(yīng)的Request Handler來處理。
更學(xué)術(shù)的,這篇文章(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上的描述類似,有多個(gè)輸入源,有多個(gè)不同的EventHandler(RequestHandler)來處理不同的請(qǐng)求,Initiation Dispatcher用于管理EventHander,EventHandler首先要注冊(cè)到Initiation Dispatcher中,然后Initiation Dispatcher根據(jù)輸入的Event分發(fā)給注冊(cè)的EventHandler;然而Initiation Dispatcher并不監(jiān)聽Event的到來,這個(gè)工作交給Synchronous Event Demultiplexer來處理。
Reactor模式結(jié)構(gòu)
在解決了什么是Reactor模式后,我們來看看Reactor模式是由什么模塊構(gòu)成。圖是一種比較簡(jiǎn)潔形象的表現(xiàn)方式,因而先上一張圖來表達(dá)各個(gè)模塊的名稱和他們之間的關(guān)系:
Handle:即操作系統(tǒng)中的句柄,是對(duì)資源在操作系統(tǒng)層面上的一種抽象,它可以是打開的文件、一個(gè)連接(Socket)、Timer等。由于Reactor模式一般使用在網(wǎng)絡(luò)編程中,因而這里一般指Socket Handle,即一個(gè)網(wǎng)絡(luò)連接(Connection,在Java NIO中的Channel)。這個(gè)Channel注冊(cè)到Synchronous Event Demultiplexer中,以監(jiān)聽Handle中發(fā)生的事件,對(duì)ServerSocketChannnel可以是CONNECT事件,對(duì)SocketChannel可以是READ、WRITE、CLOSE事件等。
Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到來,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的執(zhí)行返回的事件類型。這個(gè)模塊一般使用操作系統(tǒng)的select來實(shí)現(xiàn)。在Java NIO中用Selector來封裝,當(dāng)Selector.select()返回時(shí),可以調(diào)用Selector的selectedKeys()方法獲取Set<SelectionKey>,一個(gè)SelectionKey表達(dá)一個(gè)有事件發(fā)生的Channel以及該Channel上的事件類型。上圖的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程如果是對(duì)的,那內(nèi)部實(shí)現(xiàn)應(yīng)該是select()方法在事件到來后會(huì)先設(shè)置Handle的狀態(tài),然后返回。不了解內(nèi)部實(shí)現(xiàn)機(jī)制,因而保留原圖。
Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注冊(cè)、移除EventHandler等;另外,它還作為Reactor模式的入口調(diào)用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,當(dāng)阻塞等待返回時(shí),根據(jù)事件發(fā)生的Handle將其分發(fā)給對(duì)應(yīng)的Event Handler處理,即回調(diào)EventHandler中的handle_event()方法。
Event Handler:定義事件處理方法:handle_event(),以供InitiationDispatcher回調(diào)使用。
Concrete Event Handler:事件EventHandler接口,實(shí)現(xiàn)特定事件處理邏輯。
Reactor模式模塊之間的交互
簡(jiǎn)單描述一下Reactor各個(gè)模塊之間的交互流程,先從序列圖開始:
1. 初始化InitiationDispatcher,并初始化一個(gè)Handle到EventHandler的Map。
2. 注冊(cè)EventHandler到InitiationDispatcher中,每個(gè)EventHandler包含對(duì)相應(yīng)Handle的引用,從而建立Handle到EventHandler的映射(Map)。
3. 調(diào)用InitiationDispatcher的handle_events()方法以啟動(dòng)Event Loop。在Event Loop中,調(diào)用select()方法(Synchronous Event Demultiplexer)阻塞等待Event發(fā)生。
4. 當(dāng)某個(gè)或某些Handle的Event發(fā)生后,select()方法返回,InitiationDispatcher根據(jù)返回的Handle找到注冊(cè)的EventHandler,并回調(diào)該EventHandler的handle_events()方法。
5. 在EventHandler的handle_events()方法中還可以向InitiationDispatcher中注冊(cè)新的Eventhandler,比如對(duì)AcceptorEventHandler來,當(dāng)有新的client連接時(shí),它會(huì)產(chǎn)生新的EventHandler以處理新的連接,并注冊(cè)到InitiationDispatcher中。
Reactor模式實(shí)現(xiàn)
在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server來分析Reactor模式,這個(gè)Logging Server的實(shí)現(xiàn)完全遵循這里對(duì)Reactor描述,因而放在這里以做參考。Logging Server中的Reactor模式實(shí)現(xiàn)分兩個(gè)部分:Client連接到Logging Server和Client向Logging Server寫Log。因而對(duì)它的描述分成這兩個(gè)步驟。Client連接到Logging Server

1. Logging Server注冊(cè)LoggingAcceptor到InitiationDispatcher。
2. Logging Server調(diào)用InitiationDispatcher的handle_events()方法啟動(dòng)。
3. InitiationDispatcher內(nèi)部調(diào)用select()方法(Synchronous Event Demultiplexer),阻塞等待Client連接。
4. Client連接到Logging Server。
5. InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的連接到來。
6. LoggingAcceptor調(diào)用accept方法accept這個(gè)新連接。
7. LoggingAcceptor創(chuàng)建新的LoggingHandler。
8. 新的LoggingHandler注冊(cè)到InitiationDispatcher中(同時(shí)也注冊(cè)到Synchonous Event Demultiplexer中),等待Client發(fā)起寫log請(qǐng)求。
Client向Logging Server寫Log

1. Client發(fā)送log到Logging server。
2. InitiationDispatcher監(jiān)測(cè)到相應(yīng)的Handle中有事件發(fā)生,返回阻塞等待,根據(jù)返回的Handle找到LoggingHandler,并回調(diào)LoggingHandler中的handle_event()方法。
3. LoggingHandler中的handle_event()方法中讀取Handle中的log信息。
4. 將接收到的log寫入到日志文件、數(shù)據(jù)庫等設(shè)備中。
3.4步驟循環(huán)直到當(dāng)前日志處理完成。
5. 返回到InitiationDispatcher等待下一次日志寫請(qǐng)求。
在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有對(duì)Reactor模式的C++的實(shí)現(xiàn)版本,多年不用C++,因而略過。
Java NIO對(duì)Reactor的實(shí)現(xiàn)
在Java的NIO中,對(duì)Reactor模式有無縫的支持,即使用Selector類封裝了操作系統(tǒng)提供的Synchronous Event Demultiplexer功能。這個(gè)Doug Lea已經(jīng)在Scalable IO In Java中有非常深入的解釋了,因而不再贅述,另外這篇文章對(duì)Doug Lea的Scalable IO In Java有一些簡(jiǎn)單解釋,至少它的代碼格式比Doug Lea的PPT要整潔一些。需要指出的是,不同這里使用InitiationDispatcher來管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment來存儲(chǔ)對(duì)應(yīng)的EventHandler,因而不需要注冊(cè)EventHandler這個(gè)步驟,或者設(shè)置Attachment就是這里的注冊(cè)。而且在這篇文章中,Doug Lea從單線程的Reactor、Acceptor、Handler實(shí)現(xiàn)這個(gè)模式出發(fā);演化為將Handler中的處理邏輯多線程化,實(shí)現(xiàn)類似Proactor模式,此時(shí)所有的IO操作還是單線程的,因而再演化出一個(gè)Main Reactor來處理CONNECT事件(Acceptor),而多個(gè)Sub Reactor來處理READ、WRITE等事件(Handler),這些Sub Reactor可以分別再自己的線程中執(zhí)行,從而IO操作也多線程化。這個(gè)最后一個(gè)模型正是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中也有相應(yīng)的描述。
EventHandler接口定義
對(duì)EventHandler的定義有兩種設(shè)計(jì)思路:single-method設(shè)計(jì)和multi-method設(shè)計(jì):A single-method interface:它將Event封裝成一個(gè)Event Object,EventHandler只定義一個(gè)handle_event(Event event)方法。這種設(shè)計(jì)的好處是有利于擴(kuò)展,可以后來方便的添加新的Event類型,然而在子類的實(shí)現(xiàn)中,需要判斷不同的Event類型而再次擴(kuò)展成 不同的處理方法,從這個(gè)角度上來說,它又不利于擴(kuò)展。另外在Netty3的使用過程中,由于它不停的創(chuàng)建ChannelEvent類,因而會(huì)引起GC的不穩(wěn)定。
A multi-method interface:這種設(shè)計(jì)是將不同的Event類型在 EventHandler中定義相應(yīng)的方法。這種設(shè)計(jì)就是Netty4中使用的策略,其中一個(gè)目的是避免ChannelEvent創(chuàng)建引起的GC不穩(wěn)定, 另外一個(gè)好處是它可以避免在EventHandler實(shí)現(xiàn)時(shí)判斷不同的Event類型而有不同的實(shí)現(xiàn),然而這種設(shè)計(jì)會(huì)給擴(kuò)展新的Event類型時(shí)帶來非常 大的麻煩,因?yàn)樗枰摻涌凇?br />
關(guān)于Netty4對(duì)Netty3的改進(jìn)可以參考這里:
為什么使用Reactor模式
歸功與Netty和Java NIO對(duì)Reactor的宣傳,本文慕名而學(xué)習(xí)的Reactor模式,因而已經(jīng)默認(rèn)Reactor具有非常優(yōu)秀的性能,然而慕名歸慕名,到這里,我還是要不得不問自己Reactor模式的好處在哪里?即為什么要使用這個(gè)Reactor模式?在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是這么說的:這些貌似是很多模式的共性:解耦、提升復(fù)用性、模塊化、可移植性、事件驅(qū)動(dòng)、細(xì)力度的并發(fā)控制等,因而并不能很好的說明什么,特別是它鼓吹的對(duì)性能的提升,這里并沒有體現(xiàn)出來。當(dāng)然在這篇文章的開頭有描述過另一種直觀的實(shí)現(xiàn):Thread-Per-Connection,即傳統(tǒng)的實(shí)現(xiàn),提到了這個(gè)傳統(tǒng)實(shí)現(xiàn)的以下問題:
對(duì)于性能,它其實(shí)就是第一點(diǎn)關(guān)于Efficiency的描述,即線程的切換、同步、數(shù)據(jù)的移動(dòng)會(huì)引起性能問題。也就是說從性能的角度上,它最大的提升就是減少了性能的使用,即不需要每個(gè)Client對(duì)應(yīng)一個(gè)線程。我的理解,其他業(yè)務(wù)邏輯處理很多時(shí)候也會(huì)用到相同的線程,IO讀寫操作相對(duì)CPU的操作還是要慢很多,即使Reactor機(jī)制中每次讀寫已經(jīng)能保證非阻塞讀寫,這里可以減少一些線程的使用,但是這減少的線程使用對(duì)性能有那么大的影響嗎?答案貌似是肯定的,這篇論文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)對(duì)隨著線程的增長(zhǎng)帶來性能降低做了一個(gè)統(tǒng)計(jì):

在這個(gè)統(tǒng)計(jì)中,每個(gè)線程從磁盤中讀8KB數(shù)據(jù),每個(gè)線程讀同一個(gè)文件,因而數(shù)據(jù)本身是緩存在操作系統(tǒng)內(nèi)部的,即減少IO的影響;所有線程是事先分配的,不會(huì)有線程啟動(dòng)的影響;所有任務(wù)在測(cè)試內(nèi)部產(chǎn)生,因而不會(huì)有網(wǎng)絡(luò)的影響。該統(tǒng)計(jì)數(shù)據(jù)運(yùn)行環(huán)境:Linux 2.2.14,2GB內(nèi)存,4-way 500MHz Pentium III。從圖中可以看出,隨著線程的增長(zhǎng),吞吐量在線程數(shù)為8個(gè)左右的時(shí)候開始線性下降,并且到64個(gè)以后而迅速下降,其相應(yīng)事件也在線程達(dá)到256個(gè)后指數(shù)上升。即1+1<2,因?yàn)榫€程切換、同步、數(shù)據(jù)移動(dòng)會(huì)有性能損失,線程數(shù)增加到一定數(shù)量時(shí),這種性能影響效果會(huì)更加明顯。
對(duì)于這點(diǎn),還可以參考C10K Problem,用以描述同時(shí)有10K個(gè)Client發(fā)起連接的問題,到2010年的時(shí)候已經(jīng)出現(xiàn)10M Problem了。
當(dāng)然也有人說:Threads are expensive are no longer valid.在不久的將來可能又會(huì)發(fā)生不同的變化,或者這個(gè)變化正在、已經(jīng)發(fā)生著?沒有做過比較仔細(xì)的測(cè)試,因而不敢隨便斷言什么,然而本人觀點(diǎn),即使線程變的影響并沒有以前那么大,使用Reactor模式,甚至?xí)rSEDA模式來減少線程的使用,再加上其他解耦、模塊化、提升復(fù)用性等優(yōu)點(diǎn),還是值得使用的。
Reactor模式的缺點(diǎn)
Reactor模式的缺點(diǎn)貌似也是顯而易見的:1. 相比傳統(tǒng)的簡(jiǎn)單模型,Reactor增加了一定的復(fù)雜性,因而有一定的門檻,并且不易于調(diào)試。
2. Reactor模式需要底層的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系統(tǒng)的select系統(tǒng)調(diào)用支持,如果要自己實(shí)現(xiàn)Synchronous Event Demultiplexer可能不會(huì)有那么高效。
3. Reactor模式在IO讀寫數(shù)據(jù)時(shí)還是在同一個(gè)線程中實(shí)現(xiàn)的,即使使用多個(gè)Reactor機(jī)制的情況下,那些共享一個(gè)Reactor的Channel如果出現(xiàn)一個(gè)長(zhǎng)時(shí)間的數(shù)據(jù)讀寫,會(huì)影響這個(gè)Reactor中其他Channel的相應(yīng)時(shí)間,比如在大文件傳輸時(shí),IO操作就會(huì)影響其他Client的相應(yīng)時(shí)間,因而對(duì)這種操作,使用傳統(tǒng)的Thread-Per-Connection或許是一個(gè)更好的選擇,或則此時(shí)使用Proactor模式。
參考
Reactor Pattern WikiPediaReactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
Scalable IO In Java
C10K Problem WikiPedia