高性能 I/O 設計模式之Reactor

高性能 I/O 設計模式 Reactor編程

 

一.Reactor模式與Proactor模式比較設計模式

       通常I/O模型分爲以下三類:同步阻塞、同步非阻塞、異步阻塞、異步非阻塞緩存

(1)同步阻塞安全

在此種方式下,用戶進程在發起一個IO操做之後,必須等待IO操做的完成,只有當真正完成了IO操做之後,用戶進程才能運行。JAVA傳統的IO模型屬於此種方式!服務器

(2)同步非阻塞網絡

在此種方式下,用戶進程發起一個IO操做之後邊可返回作其它事情,可是用戶進程須要時不時的詢問IO操做是否就緒,這就要求用戶進程不停的去詢問,從而引入沒必要要的CPU資源浪費。其中目前JAVA的NIO就屬於同步非阻塞IO。多線程

(3)異步阻塞併發

此種方式下是指應用發起一個IO操做之後,不等待內核IO操做的完成,等內核完成IO操做之後會通知應用程序,這其實就是同步和異步最關鍵的區別,同步必須等待或者主動的去詢問IO是否完成,那麼爲何說是阻塞的呢?由於此時是經過select系統調用來完成的,而select函數自己的實現方式是阻塞的,而採用select函數有個好處就是它能夠同時監聽多個文件句柄(若是從UNP的角度看,select屬於同步操做。由於select以後,進程還須要讀寫數據),從而提升系統的併發性!異步

(4)異步非阻塞socket

在此種模式下,用戶進程只須要發起一個IO操做而後當即返回,等IO操做真正的完成之後,應用程序會獲得IO操做完成的通知,此時用戶進程只須要對數據進行處理就行了,不須要進行實際的IO讀寫操做,由於真正的IO讀取或者寫入操做已經由內核完成了。目前Java中尚未支持此種IO模型。

注:其實阻塞與非阻塞均可以理解爲同步範疇下才有的概念,對於異步,就不會再去分阻塞非阻塞。對於用戶進程,接到異步通知後,就直接操做進程用戶態空間裏的數據好了。

接下來用讀操做和寫操做來講明下Reactor模式和Proactor模式的區別

Reactor模式:

讀取操做:

1. 應用程序註冊讀就緒事件和相關聯的事件處理器

2. 事件分離器等待事件的發生

3. 當發生讀就緒事件的時候,事件分離器調用第一步註冊的事件處理器

4. 事件處理器首先執行實際的讀取操做,而後根據讀取到的內容進行進一步的處理

備註:寫操做與讀取操做相似

Proactor模式:

讀取操做:

1. 應用程序初始化一個異步讀取操做,而後註冊相應的事件處理器,此時事件處理器不關注讀取就緒事件,而是關注讀取完成事件,這是區別於Reactor的關鍵。

2. 事件分離器等待讀取操做完成事件

3. 在事件分離器等待讀取操做完成的時候,操做系統調用內核線程完成讀取操做(異步IO都是操做系統負責將數據讀寫到應用傳遞進來的緩衝區供應用程序操做,操做系統扮演了重要角色),並將讀取的內容放入用戶傳遞過來的緩存區中。這也是區別於Reactor的一點,Proactor中,應用程序須要傳遞緩存區。

4. 事件分離器捕獲到讀取完成事件後,激活應用程序註冊的事件處理器,事件處理器直接從緩存區讀取數據,而不須要進行實際的讀取操做。

備註:Proactor中寫入操做和讀取操做,只不過感興趣的事件是寫入完成事件。

從上總結以下:

1Reactor 和 Proactor 是基於事件驅動

2Reactor和Proactor模式的主要區別就是真正的讀取和寫入操做是有Who來完成的,Reactor中須要應用程序本身讀取或者寫入數據,而Proactor模式中,應用程序不須要進行實際的讀寫過程,它只須要從緩存區讀取或者寫入便可,操做系統會讀取緩存區或者寫入緩存區到真正的IO設備.

二.Reactor模式

Reactor模式首先是事件驅動的,有一個或多個併發輸入源,有一個Service Handler,有多個Request Handlers;這個Service Handler會同步的將輸入的請求(Event)多路複用的分發給相應的Request Handler。若是用圖來表達:

 

從結構上,這有點相似生產者消費者模式,Reactor模式每當一個Event輸入到Service Handler以後,該Service Handler會主動的根據不一樣的Event類型將其分發給對應的Request Handler來處理。

2.1 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接口,實現特定事件處理邏輯。

 

2.2 業務流程及時序圖

 

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模式三種實現模式

3.1 Reactor模式的第一種實現模式(單線程模式)

 

因爲Reactor模式使用的是異步非阻塞IO,全部的IO操做都不會被阻塞,理論上一個線程能夠獨立處理全部的IO操做。包括一個線程池處理connect事件(Acceptor),一個線程池處理Read事件,一個線程池處理Write事件,雖然Reactor Thread是單線程的,可是從Reactor Thread到Handler都是異步的,從而I/O都是多線程化的。

對於一些小容量應用場景,可使用到單線程模型。但對於高負載,大併發的應用卻不合適,主要緣由以下:

1)、當一個NIO線程同時處理成百上千的鏈路,性能上沒法支撐,即便NIO線程的CPU負荷達到100%,也沒法徹底處理消息

2)、當NIO線程負載太重後,處理速度會變慢,會致使大量客戶端鏈接超時,超時以後每每會重發,更加劇了NIO線程的負載。

3)、可靠性低,一個線程意外死循環,會致使整個通訊系統不可用

因爲Reactor單線程模式有以上的缺點,因此就有了下面的Reactor多線程模式

3.2 Reactor模式的第二種實現模式(多線程模式)

 

 

如上所示,經過Reactor Thread Pool來提升Event的分發能力,在絕大多數場景下,該模型都能知足性能需求。可是,在一些特殊的應用場景下,如服務器會對客戶端的握手消息進行安全認證。這類場景下,單獨的一個Acceptor線程可能會存在性能不足的問題。

3.3 Reactor模式的第三種實現模式(主從模式)

 

 

與Reactor多線程模式不一樣之處,主從模式有兩個,mainReactor主要負責監聽Server Socket、Accept鏈接以及安全認證,而且將創建好的socket分派給subReactor,subReactor負責多路分離已鏈接的socket,讀寫網絡數據,對業務處理功能,其扔給工程線程池(worker)完成。通常狀況下,subReactor個數能夠與CPU個數相同。

 

四.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模式。

相關文章
相關標籤/搜索