講到高性能IO繞不開Reactor模式,它是大多數IO相關組件如Netty、Redis在使用的IO模式,爲何須要這種模式,它是如何設計來解決高性能併發的呢?html
最最原始的網絡編程思路就是服務器用一個while循環,不斷監聽端口是否有新的套接字鏈接,若是有,那麼就調用一個處理函數處理,相似:while(true){ socket = accept(); handle(socket) } 這種方法的最大問題是沒法併發,效率過低,若是當前的請求沒有處理完,那麼後面的請求只能被阻塞,服務器的吞吐量過低。以後,想到了使用多線程,也就是很經典的connection per thread,每個鏈接用一個線程處理,相似:while(true){ socket = accept(); new thread(socket); } tomcat服務器的早期版本確實是這樣實現的。多線程的方式確實必定程度上極大地提升了服務器的吞吐量,由於以前的請求在read阻塞之後,不會影響到後續的請求,由於他們在不一樣的線程中。這也是爲何一般會講「一個線程只能對應一個socket」的緣由。最開始對這句話很不理解,線程中建立多個socket不行嗎?語法上確實能夠,可是實際上沒有用,每個socket都是阻塞的,因此在一個線程裏只能處理一個socket,就算accept了多個也沒用,前一個socket被阻塞了,後面的是沒法被執行到的。缺點在於資源要求過高,系統中建立線程是須要比較高的系統資源的,若是鏈接數過高,系統沒法承受,並且,線程的反覆建立-銷燬也須要代價。線程池自己能夠緩解線程建立-銷燬的代價,這樣優化確實會好不少,不過仍是存在一些問題的,就是線程的粒度太大。每個線程把一次交互的事情所有作了,包括讀取和返回,甚至鏈接,表面上彷佛鏈接不在線程裏,可是若是線程不夠,有了新的鏈接,也沒法獲得處理,因此,目前的方案線程裏能夠當作要作三件事,鏈接,讀取和寫入。線程同步的粒度太大了,限制了吞吐量。應該把一次鏈接的操做分爲更細的粒度或者過程,這些更細的粒度是更小的線程。整個線程池的數目會翻倍,可是線程更簡單,任務更加單一。這其實就是Reactor出現的緣由,在Reactor中,這些被拆分的小線程或者子過程對應的是handler,每一種handler會出處理一種event。這裏會有一個全局的管理者selector,咱們須要把channel註冊感興趣的事件,那麼這個selector就會不斷在channel上檢測是否有該類型的事件發生,若是沒有,那麼主線程就會被阻塞,不然就會調用相應的事件處理函數即handler來處理。典型的事件有鏈接,讀取和寫入,固然咱們就須要爲這些事件分別提供處理器,每個處理器能夠採用線程的方式實現。一個鏈接來了,顯示被讀取線程或者handler處理了,而後再執行寫入,那麼以前的讀取就能夠被後面的請求複用,吞吐量就提升了。
幾乎全部的網絡鏈接都會通過讀請求內容——》解碼——》計算處理——》編碼回覆——》回覆的過程,Reactor模式的的演化過程以下:
這種模型因爲IO在阻塞時會一直等待,所以在用戶負載增長時,性能降低的很是快。
server致使阻塞的緣由:
一、serversocket的accept方法,阻塞等待client鏈接,直到client鏈接成功。
二、線程從socket inputstream讀入數據,會進入阻塞狀態,直到所有數據讀完。
三、線程向socket outputstream寫入數據,會阻塞直到所有數據寫完。
改進:採用基於事件驅動的設計,當有事件觸發時,纔會調用處理器進行數據處理。
Reactor:負責響應IO事件,當檢測到一個新的事件,將其發送給相應的Handler去處理。
Handler:負責處理非阻塞的行爲,標識系統管理的資源;同時將handler與事件綁定。
Reactor爲單個線程,須要處理accept鏈接,同時發送請求處處理器中。
因爲只有單個線程,因此處理器中的業務須要可以快速處理完。
改進:使用多線程處理業務邏輯。
將處理器的執行放入線程池,多線程進行業務處理。但Reactor仍爲單個線程。
繼續改進:對於多個CPU的機器,爲充分利用系統資源,將Reactor拆分爲兩部分。
Using Multiple Reactors
Using Multiple Reactors
mainReactor負責監聽鏈接,accept鏈接給subReactor處理,爲何要單獨分一個Reactor來處理監聽呢?由於像TCP這樣須要通過3次握手才能創建鏈接,這個創建鏈接的過程也是要耗時間和資源的,單獨分一個Reactor來處理,能夠提升性能。
Reactor模式是什麼,有哪些優缺點?
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。若是用圖來表達:
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來處理。
從結構上,這有點相似生產者消費者模式,即有一個或多個生產者將事件放入一個Queue中,而一個或多個消費者主動的從這個Queue中Poll事件來處理;而Reactor模式則並無Queue來作緩衝,每當一個Event輸入到Service Handler以後,該Service Handler會主動的根據不一樣的Event類型將其分發給對應的Request Handler來處理。
在解決了什麼是Reactor模式後,咱們來看看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接口,實現特定事件處理邏輯。
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接口,實現特定事件處理邏輯。
優勢
1)響應快,沒必要爲單個同步時間所阻塞,雖然Reactor自己依然是同步的; 2)編程相對簡單,能夠最大程度的避免複雜的多線程及同步問題,而且避免了多線程/進程的切換開銷; 3)可擴展性,能夠方便的經過增長Reactor實例個數來充分利用CPU資源; 4)可複用性,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模式。
原文連接:https://www.cnblogs.com/doit8791/p/7461479.html