為什么說Netty是性能之王,因為它用了 Reactor 模型啊
本文將介紹基于進程/線程模型,服務器如何處理請求。值得說明的是,具體選擇線程還是進程,更多是與平臺及編程語言相關。
例如 C 語言使用線程和進程都可以(例如 Nginx 使用進程,Memcached 使用線程),Java 語言一般使用線程(例如 Netty),為了描述方便,下面都使用線程來進行描述。
1、線程模型1:傳統(tǒng)阻塞 I/O 服務模型

特點:
1)采用阻塞式 I/O 模型獲取輸入數(shù)據(jù);
2)每個連接都需要獨立的線程完成數(shù)據(jù)輸入,業(yè)務處理,數(shù)據(jù)返回的完整操作。
存在問題:
1)當并發(fā)數(shù)較大時,需要創(chuàng)建大量線程來處理連接,系統(tǒng)資源占用較大;
2)連接建立后,如果當前線程暫時沒有數(shù)據(jù)可讀,則線程就阻塞在 Read 操作上,造成線程資源浪費。
2、線程模型2:Reactor 模式
2.1 基本介紹
針對傳統(tǒng)阻塞 I/O 服務模型的 2 個缺點,比較常見的有如下解決方案:
1)基于 I/O 復用模型:多個連接共用一個阻塞對象,應用程序只需要在一個阻塞對象上等待,無需阻塞等待所有連接。當某條連接有新的數(shù)據(jù)可以處理時,操作系統(tǒng)通知應用程序,線程從阻塞狀態(tài)返回,開始進行業(yè)務處理;
2)基于線程池復用線程資源:不必再為每個連接創(chuàng)建線程,將連接完成后的業(yè)務處理任務分配給線程進行處理,一個線程可以處理多個連接的業(yè)務。
I/O 復用結合線程池,這就是 Reactor 模式基本設計思想,如下圖:

Reactor 模式,是指通過一個或多個輸入同時傳遞給服務處理器的服務請求的事件驅動處理模式。
服務端程序處理傳入多路請求,并將它們同步分派給請求對應的處理線程,Reactor 模式也叫 Dispatcher 模式。
即 I/O 多了復用統(tǒng)一監(jiān)聽事件,收到事件后分發(fā)(Dispatch 給某進程),是編寫高性能網(wǎng)絡服務器的必備技術之一。
Reactor 模式中有 2 個關鍵組成:
1)Reactor:Reactor 在一個單獨的線程中運行,負責監(jiān)聽和分發(fā)事件,分發(fā)給適當?shù)奶幚沓绦騺韺?IO 事件做出反應。它就像公司的電話接線員,它接聽來自客戶的電話并將線路轉移到適當?shù)穆?lián)系人;
2)Handlers:處理程序執(zhí)行 I/O 事件要完成的實際事件,類似于客戶想要與之交談的公司中的實際官員。Reactor 通過調度適當?shù)奶幚沓绦騺眄憫?I/O 事件,處理程序執(zhí)行非阻塞操作。
根據(jù) Reactor 的數(shù)量和處理資源池線程的數(shù)量不同,有 3 種典型的實現(xiàn):
1)單 Reactor 單線程;
2)單 Reactor 多線程;
3)主從 Reactor 多線程。
下面詳細介紹這 3 種實現(xiàn)方式。
2.2 單 Reactor 單線程

其中,Select 是前面 I/O 復用模型介紹的標準網(wǎng)絡編程 API,可以實現(xiàn)應用程序通過一個阻塞對象監(jiān)聽多路連接請求,其他方案示意圖類似。
方案說明:
1)Reactor 對象通過 Select 監(jiān)控客戶端請求事件,收到事件后通過 Dispatch 進行分發(fā);
2)如果是建立連接請求事件,則由 Acceptor 通過 Accept 處理連接請求,然后創(chuàng)建一個 Handler 對象處理連接完成后的后續(xù)業(yè)務處理;
3)如果不是建立連接事件,則 Reactor 會分發(fā)調用連接對應的 Handler 來響應;
4)Handler 會完成 Read→業(yè)務處理→Send 的完整業(yè)務流程。
優(yōu)點:模型簡單,沒有多線程、進程通信、競爭的問題,全部都在一個線程中完成。
缺點:性能問題,只有一個線程,無法完全發(fā)揮多核 CPU 的性能。Handler 在處理某個連接上的業(yè)務時,整個進程無法處理其他連接事件,很容易導致性能瓶頸。
可靠性問題,線程意外跑飛,或者進入死循環(huán),會導致整個系統(tǒng)通信模塊不可用,不能接收和處理外部消息,造成節(jié)點故障。
使用場景:客戶端的數(shù)量有限,業(yè)務處理非常快速,比如 Redis,業(yè)務處理的時間復雜度 O(1)。
2.3單 Reactor 多線程

方案說明:
1)Reactor 對象通過 Select 監(jiān)控客戶端請求事件,收到事件后通過 Dispatch 進行分發(fā);
2)如果是建立連接請求事件,則由 Acceptor 通過 Accept 處理連接請求,然后創(chuàng)建一個 Handler 對象處理連接完成后續(xù)的各種事件;
3)如果不是建立連接事件,則 Reactor 會分發(fā)調用連接對應的 Handler 來響應;
4)Handler 只負責響應事件,不做具體業(yè)務處理,通過 Read 讀取數(shù)據(jù)后,會分發(fā)給后面的 Worker 線程池進行業(yè)務處理;
5)Worker 線程池會分配獨立的線程完成真正的業(yè)務處理,如何將響應結果發(fā)給 Handler 進行處理;
6)Handler 收到響應結果后通過 Send 將響應結果返回給 Client。
優(yōu)點:可以充分利用多核 CPU 的處理能力。
缺點:多線程數(shù)據(jù)共享和訪問比較復雜;Reactor 承擔所有事件的監(jiān)聽和響應,在單線程中運行,高并發(fā)場景下容易成為性能瓶頸。
2.4 主從 Reactor 多線程

針對單 Reactor 多線程模型中,Reactor 在單線程中運行,高并發(fā)場景下容易成為性能瓶頸,可以讓 Reactor 在多線程中運行。
方案說明:
1)Reactor 主線程 MainReactor 對象通過 Select 監(jiān)控建立連接事件,收到事件后通過 Acceptor 接收,處理建立連接事件;
2)Acceptor 處理建立連接事件后,MainReactor 將連接分配 Reactor 子線程給 SubReactor 進行處理;
3)SubReactor 將連接加入連接隊列進行監(jiān)聽,并創(chuàng)建一個 Handler 用于處理各種連接事件;
4)當有新的事件發(fā)生時,SubReactor 會調用連接對應的 Handler 進行響應;
5)Handler 通過 Read 讀取數(shù)據(jù)后,會分發(fā)給后面的 Worker 線程池進行業(yè)務處理;
6)Worker 線程池會分配獨立的線程完成真正的業(yè)務處理,如何將響應結果發(fā)給 Handler 進行處理;
7)Handler 收到響應結果后通過 Send 將響應結果返回給 Client。
優(yōu)點:父線程與子線程的數(shù)據(jù)交互簡單職責明確,父線程只需要接收新連接,子線程完成后續(xù)的業(yè)務處理。
父線程與子線程的數(shù)據(jù)交互簡單,Reactor 主線程只需要把新連接傳給子線程,子線程無需返回數(shù)據(jù)。
這種模型在許多項目中廣泛使用,包括 Nginx 主從 Reactor 多進程模型,Memcached 主從多線程,Netty 主從多線程模型的支持。
2.5 小結
3種模式可以用個比喻來理解:(餐廳常常雇傭接待員負責迎接顧客,當顧客入坐后,侍應生專門為這張桌子服務)
1)單 Reactor 單線程,接待員和侍應生是同一個人,全程為顧客服務;
2)單 Reactor 多線程,1 個接待員,多個侍應生,接待員只負責接待;
3)主從 Reactor 多線程,多個接待員,多個侍應生。
Reactor 模式具有如下的優(yōu)點:
1)響應快,不必為單個同步時間所阻塞,雖然 Reactor 本身依然是同步的;
2)編程相對簡單,可以最大程度的避免復雜的多線程及同步問題,并且避免了多線程/進程的切換開銷;
3)可擴展性,可以方便的通過增加 Reactor 實例個數(shù)來充分利用 CPU 資源;
4)可復用性,Reactor 模型本身與具體事件處理邏輯無關,具有很高的復用性。
3、線程模型2:Proactor 模型
在 Reactor 模式中,Reactor 等待某個事件或者可應用或者操作的狀態(tài)發(fā)生(比如文件描述符可讀寫,或者是 Socket 可讀寫)。
然后把這個事件傳給事先注冊的 Handler(事件處理函數(shù)或者回調函數(shù)),由后者來做實際的讀寫操作。
其中的讀寫操作都需要應用程序同步操作,所以 Reactor 是非阻塞同步網(wǎng)絡模型。
如果把 I/O 操作改為異步,即交給操作系統(tǒng)來完成就能進一步提升性能,這就是異步網(wǎng)絡模型 Proactor。

Proactor 是和異步 I/O 相關的,詳細方案如下:
1)Proactor Initiator 創(chuàng)建 Proactor 和 Handler 對象,并將 Proactor 和 Handler 都通過 AsyOptProcessor(Asynchronous Operation Processor)注冊到內核;
2)AsyOptProcessor 處理注冊請求,并處理 I/O 操作;
3)AsyOptProcessor 完成 I/O 操作后通知 Proactor;
4)Proactor 根據(jù)不同的事件類型回調不同的 Handler 進行業(yè)務處理;
5)Handler 完成業(yè)務處理。
可以看出 Proactor 和 Reactor 的區(qū)別:
1)Reactor 是在事件發(fā)生時就通知事先注冊的事件(讀寫在應用程序線程中處理完成);
2)Proactor 是在事件發(fā)生時基于異步 I/O 完成讀寫操作(由內核完成),待 I/O 操作完成后才回調應用程序的處理器來進行業(yè)務處理。
理論上 Proactor 比 Reactor 效率更高,異步 I/O 更加充分發(fā)揮 DMA(Direct Memory Access,直接內存存取)的優(yōu)勢。
但是Proactor有如下缺點:
1)編程復雜性,由于異步操作流程的事件的初始化和事件完成在時間和空間上都是相互分離的,因此開發(fā)異步應用程序更加復雜。應用程序還可能因為反向的流控而變得更加難以 Debug;
2)內存使用,緩沖區(qū)在讀或寫操作的時間段內必須保持住,可能造成持續(xù)的不確定性,并且每個并發(fā)操作都要求有獨立的緩存,相比 Reactor 模式,在 Socket 已經(jīng)準備好讀或寫前,是不要求開辟緩存的;
3)操作系統(tǒng)支持,Windows 下通過 IOCP 實現(xiàn)了真正的異步 I/O,而在 Linux 系統(tǒng)下,Linux 2.6 才引入,目前異步 I/O 還不完善。
因此在 Linux下實現(xiàn)高并發(fā)網(wǎng)絡編程都是以 Reactor 模型為主。
