前言
從最早看的《Head First 設計模式》到後面看的一些書,裡面對於設計模式的講解大部分都是從抽象概念介紹一個設計模式是什麼和一些例子。這些例子大多都是買咖啡(head first真的喜歡以咖啡舉例),玩具鴨子之類的。我也看過類似《Javascript設計模式與開發實踐》,裡面舉例的時候會以一些作者實際開發中碰到的例子。這個系列是我日常學習中遇到設計模式的一個總結,希望能幫助到大家。
文章應該會在我的學習過程中不斷更新。
正文
今天在研究《Node.js design patterns 2nd》的時候提到了反應器模式,我總結下自己所學和研究並寫篇部落格總結一下。
反應器模式的介紹
反應器設計模式(Reactor pattern)是一種為處理服務請求併發 提交到一個或者多個服務處理程式的事件設計模式。當請求抵達後,服務處理程式使用解多路分配策略,然後同步地派發這些請求至相關的請求處理程式。
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers. --GoF
反正我們不玩定義。我們先以一個餐飲店為例,假設每一個人來就餐就是一個I/O操作,他會先看一下選單,然後點餐。就像一個網站會有很多的請求,要求伺服器做一些事情。處理這些就餐事件的就是我們需要解決的問題了。
多執行緒的併發解決方案
在多執行緒處理的方式會是這樣的:
一個人來就餐,一個服務員去服務,然後客人點菜。 服務員將選單給後廚。
十個人來就餐,十個服務員去服務……
這個就是多執行緒的處理方式,一個請求到來,就會有一個執行緒服務。很顯然這種方式在人少的情況下會有很好的使用者體驗,每個客人都感覺自己是VIP,專人服務的。如果餐廳一直這樣同一時間最多來10個客人,這家餐廳是可以很好的服務下去的。
那麼問題來了,如果這家餐廳生意非常好,同時就餐人數(網站併發訪問量)達到100人呢,給每個分配個服務員那餐館怕不是要破產。
儘管可以考慮使用類似執行緒池的方法,組成一個10個服務員的執行緒池,一個服務員服務完一個人後繼續去服務下一個。 但是這樣有一個比較嚴重的缺點就是,如果某個客人點菜很慢(讀寫檔案,網路請求等操作和記憶體讀寫比起來非常的慢),其他人可能就要等好長時間了。
事件驅動的解決方案
其實當客人在點菜的時候,大部服務員服務的時候都在等著客人的菜上好後才去服務下一個(阻塞),其實幹的活不是太多。如果客人需要點餐的時候直接告訴服務員,我需要宮保雞丁,做好之後直接放到14桌,然後服務員就可以去服務下一個人了,這樣是不是能夠有效利用服務員的資源?
其實這就是基於事件的解決方案,“做好之後放到14桌”其實就是回撥函式。
反應器模式
先直接貼一下Douglas C. Schmidt 大佬的論文 An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events.pdf
Node.js中的反應器模式(Reactor pattern)
Node.js 這幾年非常火,藉助 js 天生的事件驅動機制和 V8高效能引擎,讓編寫高併發的web應用門檻降低了許多,當然這背後還要得益於基於事件驅動的 Reactor 模式,讓本身只支援單執行緒執行的 js 能夠勝任如今高併發環境下的服務端應用。
下面這張圖就是一個應用裡面使用Reactor模式的圖示
當應用使用Reactor模式的時候發生瞭如下過程:- 應用通過向Event Demultiplexer提交一個請求生成一個新的I/O操作。應用同時指定一個handler,這個handler會在操作完成時被呼叫,其實就是回撥函式或者叫事件處理函式。向Event Demultiplexer提交一個請求是非阻塞(non-blocking)的呼叫,會立即返回。
- 當一系列I/O操作完成後,Event Demultiplexer會把對應觸發的event的項推進Event Queue。
- 與此同時,Event Loop會遍歷所有Event Queue裡面的每一項。
- 對於每個事件event,相應的處理程式handler會被執行。
- 當處理程式handler執行完後會把控制流交還給Event Loop(5a),然而有可能處理程式中會產生新的非同步操作(5b),導致在控制流回到Event Loop之前會向Event Demultiplexer中插入新的項。
- 當Event Queue中的所有項都執行完後,這個迴圈會在Event Demultiplexer中被關閉,當再次有新事件被加入Event Queue時再觸發另一個迴圈。
前端的同學對於Event Loop應該比較熟吧。
現在再來重新看一下定義是不是清晰了一點?
反應器設計模式(Reactor pattern)是一種為處理服務請求併發 提交到一個或者多個服務處理程式的事件設計模式。當請求抵達後,服務處理程式使用解多路分配策略,然後同步地派發這些請求至相關的請求處理程式。
隨便扯扯
其實Event Demultiplexer之前我就有過一些瞭解不過沒有深入,當時是在做計算機網路大作業的時候,當時選題是做一個Web Server,類似Apache、Nginx那種。當時研究高併發模型的時候接觸到了I/O多路複用(multiplexing),其實就是事件驅動,當時用的libevent的庫,好像是基於的linux的epoll系統呼叫實現的。