EDA风格与Reactor模式
來源:https://www.cnblogs.com/ivaneye/p/10129896.html
本文將探討如下幾個問題:
- Event-Driven架構風格的約束
- EDA風格對架構屬性的影響
- Reactor架構模式
- Reactor所解決的問題
- redis中的EventDriven
從觀察者模式到EDA風格
GOF的23種設計模式中,有一個觀察者模式!個人覺得叫「監聽模式」更合理!
定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時, 所有依賴于它的對象都得到通知并被自動更新。
它和EDA風格有些類似!不過一個應用在代碼關系層面;一個應用在架構上!
在EDA中,有三個必要組件:
- 事件生產組件:對應到觀察者模式中,就是引起Subject狀態變化的對象。它引起Subject狀態變化的動作就是事件。
- 事件隊列:對應到觀察者模式中,就是Subject對象。事件生產者產生的事件,會到事件隊列中。
- 事件處理組件:處理事件生產組件所產生的事件。對應到觀察者模式中,就是Observer對象。
EDA風格的約束
- 「事件生產組件」產生事件,將其添加到「事件隊列」中
- 「事件處理組件」監聽「事件隊列」
- 當「事件隊列」中有自己能處理的事件時,「事件處理組件」將對該事件進行處理
- 「事件處理組件」處理完事件后,可以將處理結果返回給「事件生產組件」,也可以不返回
- 「事件生產組件」可以接收「事件處理組件」處理結果,也可以不接收
EDA風格可以細分為Mediator結構和Broker結構!
- Mediator結構通過一個Mediator組件協調多個步驟間的關系和執行順序
- Broker結構則是通過松散的方式來組織多個步驟之間的關系和執行順序
Mediator結構如下:
在Mediator結構中主要有四個組件:
- 事件隊列(event queue)
- 事件中介(event mediator)
- 事件通道(event channel)
- 事件處理器(event processor)。
執行流程如下:
- 「事件生產組件」產生事件,將其添加到「事件隊列」中
- 「事件中介」監聽「事件隊列」
- 當「事件隊列」中有事件時,「事件中介」根據具體的事件,將其拆分/組合為一步步的具體步驟
- 將具體的步驟添加到對應的「事件通道」中
- 「事件處理器」從「事件通道」中獲取到事件,進行處理
Broker結構如下:
Broker結構中主要包括兩個組件:
- Broker:Broker可被集中或相互關聯在一起使用,此外,Broker中還可以包含所有事件流中使用的事件通道。
- 事件處理器
執行流程如下:
- 「事件生產組件」產生事件,將其添加到「Broker」中
- 「事件處理組件」監聽「Broker」
- 當「Broker」中有自己能處理的事件時,「事件處理組件」將對該事件進行處理
- 「事件處理組件」處理完事件后,發送后續事件到「Broker」中
- 其它「事件處理組件」接收到自己能處理的事件后,對該事件進行處理,處理完后,可能繼續發送事件到「Broker」中
- 當不再有后續事件時,所有步驟完成
EDA風格對架構屬性的影響
- 性能:由于EDA是個異步架構,對于不需要返回值的請求,它能明顯的提高用戶可感知的性能。
- 擴展性:事件生產者和事件消費者松耦合,可以獨立進化,可以方便的進行功能擴展。同時由于,可以方便的添加事件消費者,故而進一步提高了擴展性。
- 伸縮性:事件生產者和事件消費者松耦合及可以方便的添加事件消費者,使得EDA有很好的伸縮性
- 可維護性:事件生產者和事件消費者松耦合,且和一般的調用方式相比,在理解上有一定的難度,使得開發難度增加,但單元測試較方便。而由于EDA的異步性,使得集成測試比較麻煩。可維護性一般
- 可運維性:事件生產者和事件消費者松耦合,可獨立部署,可運維性相對較容易
- 靈活性:高度可擴展,易于伸縮使得EDA有較高的靈活性
Reactor架構模式
Reactor架構模式,是EDA風格的一種實現!它是為了處理:
- 高并發IO請求
- 其中請求所包含的數據量不大
- 每個請求處理耗時不長
Reactor模型,有四個組件:
- Acceptor:Acceptor接受Client連接。屬于EDA「事件隊列組件」。
- Channel:接收IO事件。屬于EDA「事件隊列組件」。
- Reactor:監聽Channel和Acceptor,當發生連接事件或IO事件時,將其派發給對應的Handler。屬于EDA「事件隊列組件」。
- Handler:和一個Client通訊的實體,進行實際的業務處理。對應EDA的「事件處理組件」。
Client屬于EDA的「事件生產組件」!
Reactor可以分為:單線程模型,多線程模型和主從Reactor模型。
- 單線程模型
Reactor輪詢到消息后,就直接委托給Handler去處理,處理完后,再進行后面的輪詢,即一個Handler處理完之后,才能進行下一個Handler的處理!如果某個handler耗時較長,就會阻塞后續的handler的執行!
- 多線程模型
Reactor多線程模型就是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發送請求就不會堵塞!
但是當用戶進一步增加的時候,Reactor會出現瓶頸!因為Reactor既要處理IO操作請求,又要響應連接請求!為了分擔Reactor的負擔,所以引入了主從Reactor模型!
- 主從Reactor模型
主Reactor用于響應連接請求,從Reactor用于處理IO操作請求!
詳細內容可參考之前的博文《高性能Server---Reactor模型》!
這里簡單說下,為什么Reactor中,請求的數據量不能太大,請求處理時間為什么不能太長!
其實很好理解,如果請求數據量太大、處理時間很長!即使是主從Reactor模型,線程池也會被耗盡!耗盡后,導致后續的請求積壓。
Reactor解決的是傳統BIO模型下,Server并發處理請求的能力受限于系統所能創建的線程數的問題!
redis中的EventDriven
redis使用了「Event-driven programming」來處理指令,「Event-driven programming」是一種編程范式,也可以說是EDA的代碼實現。redis中的實現,類似Reactor單線程模型!
事件驅動程序設計是一種計算機程序設計模型。與傳統上一次等待一個完整的指令然后再做運行的方式不同,事件驅動程序模型下的系統,基本上的架構是預先設計一個事件循環所形成的程序,這個事件循環程序不斷地檢查目前要處理的信息,根據要處理的信息運行一個觸發函數進行必要的處理。其中這個外部信息可能來自一個目錄夾中的文件,可能來自鍵盤或鼠標的動作,或者是一個時間事件。
簡單的看一下代碼:
// server.cint main(int argc, char **argv) {......aeMain(server.el); // 進入事件監聽輪詢,輪詢注冊上來的文件句柄...... }// ae.cvoid aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0; // eventLoop相當于Reactor模式中的Reactor組件while (!eventLoop->stop) {if (eventLoop->beforesleep != NULL)eventLoop->beforesleep(eventLoop);aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);} }int aeProcessEvents(aeEventLoop *eventLoop, int flags) {......numevents = aeApiPoll(eventLoop, tvp);// select,epoll,evport,kqueue底層實現,獲取事件......// 將事件委托給具體的Handler去處理for (j = 0; j < numevents; j++) {aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];int mask = eventLoop->fired[j].mask;int fd = eventLoop->fired[j].fd;int fired = 0; int invert = fe->mask & AE_BARRIER;if (!invert && fe->mask & mask & AE_READABLE) {fe->rfileProc(eventLoop,fd,fe->clientData,mask);fired++;}if (fe->mask & mask & AE_WRITABLE) {if (!fired || fe->wfileProc != fe->rfileProc) {fe->wfileProc(eventLoop,fd,fe->clientData,mask);fired++;}}if (invert && fe->mask & mask & AE_READABLE) {if (!fired || fe->wfileProc != fe->rfileProc) {fe->rfileProc(eventLoop,fd,fe->clientData,mask);fired++;}}processed++;}...... }// ae.h // aeFileEvent的結構如下,在構建時,就將處理程序賦給了其中的aeFileProc // 在上面的輪詢中,直接取出來執行即可 typedef struct aeFileEvent {int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */aeFileProc *rfileProc;aeFileProc *wfileProc;void *clientData; } aeFileEvent;下面以我們啟動redis-server和redis-cli這個流程,來簡單的說明下redis的執行流程:
//redis-server啟動后,進入輪詢狀態,等待事件 //啟動時構建了一個tcpHandler,用于處理客戶端連接// server.c for (j = 0; j < server.ipfd_count; j++) {if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL) == AE_ERR){serverPanic("Unrecoverable error creating server.ipfd file event.");} }//redis-cli啟動后,eventLoop輪詢到連接事件,觸發tcpHandler,根據獲取到的文件句柄,構建aeFileEvent,同時設置readQueryFromClient Handler,用于處理后續的客戶端的指令 client *createClient(int fd) {......if (aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c) == AE_ERR){close(fd);zfree(c);return NULL;}...... }// 客戶端發送指令后,觸發readQueryFromClient Handler,來處理指令 // 處理完后,等待下一個指令參考資料
- Event-driven architecture
- Event-driven architecture
- Reactor pattern
- 《Software Architecture Patterns》Mark Richards
- Event-driven programming
- Redis源碼
- 《恰如其分的軟件架構》
?
?
?
總結
以上是生活随笔為你收集整理的EDA风格与Reactor模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java消息服务~开发者分配的消息头
- 下一篇: java 防止sql xxs注入,J