1. 程式人生 > 程式設計 >簡單瞭解Java Netty Reactor三種執行緒模型

簡單瞭解Java Netty Reactor三種執行緒模型

1. Reactor三種執行緒模型

1.1. 單執行緒模型

Reactor單執行緒模型,指的是所有的IO操作都在同一個NIO執行緒上面完成,NIO執行緒的職責如下:

1)作為NIO服務端,接收客戶端的TCP連線;
2)作為NIO客戶端,向服務端發起TCP連線;
3)讀取通訊對端的請求或者應答訊息;
4)向通訊對端傳送訊息請求或者應答訊息。

Reactor單執行緒模型示意圖如下所示:

簡單瞭解Java Netty Reactor三種執行緒模型

Reactor單執行緒模型

由於Reactor模式使用的是非同步非阻塞IO,所有的IO操作都不會導致阻塞,理論上一個執行緒可以獨立處理所有IO相關的操作。從架構層面看,一個NIO執行緒確實可以完成其承擔的職責。例如,通過Acceptor類接收客戶端的TCP連線請求訊息,鏈路建立成功之後,通過Dispatch將對應的ByteBuffer派發到指定的Handler上進行訊息解碼。使用者執行緒可以通過訊息編碼通過NIO執行緒將訊息傳送給客戶端。

對於一些小容量應用場景,可以使用單執行緒模型。但是對於高負載、大併發的應用場景卻不合適,主要原因如下:
1)一個NIO執行緒同時處理成百上千的鏈路,效能上無法支撐,即便NIO執行緒的CPU負荷達到100%,也無法滿足海量訊息的編碼、解碼、讀取和傳送;
2)當NIO執行緒負載過重之後,處理速度將變慢,這會導致大量客戶端連線超時,超時之後往往會進行重發,這更加重了NIO執行緒的負載,最終會導致大量訊息積壓和處理超時,成為系統的效能瓶頸;
3)可靠性問題:一旦NIO執行緒意外跑飛,或者進入死迴圈,會導致整個系統通訊模組不可用,不能接收和處理外部訊息,造成節點故障。

為了解決這些問題,演進出了Reactor多執行緒模型,如下。

1.2. 多執行緒模型

Rector多執行緒模型與單執行緒模型最大的區別就是有一組NIO執行緒處理IO操作,它的原理圖如下:

簡單瞭解Java Netty Reactor三種執行緒模型

Rector多執行緒模型

Reactor多執行緒模型的特點:

1)有專門一個NIO執行緒-Acceptor執行緒用於監聽服務端,接收客戶端的TCP連線請求;
2)網路IO操作-讀、寫等由一個NIO執行緒池負責,執行緒池可以採用標準的JDK執行緒池實現,它包含一個任務佇列和N個可用的執行緒,由這些NIO執行緒負責訊息的讀取、解碼、編碼和傳送;
3)1個NIO執行緒可以同時處理N條鏈路,但是1個鏈路只對應1個NIO執行緒,防止發生併發操作問題。

在絕大多數場景下,Reactor多執行緒模型都可以滿足效能需求;但是,在極個別特殊場景中,一個NIO執行緒負責監聽和處理所有的客戶端連線可能會存在效能問題。例如併發百萬客戶端連線,或者服務端需要對客戶端握手進行安全認證,但是認證本身非常損耗效能。在這類場景下,單獨一個Acceptor執行緒可能會存在效能不足問題,為了解決效能問題,產生了第三種Reactor執行緒模型-主從Reactor多執行緒模型。

1.3. 主從多執行緒模型

主從Reactor執行緒模型的特點是:服務端用於接收客戶端連線的不再是個1個單獨的NIO執行緒,而是一個獨立的NIO執行緒池。Acceptor接收到客戶端TCP連線請求處理完成後(可能包含接入認證等),將新建立的SocketChannel註冊到IO執行緒池(sub reactor執行緒池)的某個IO執行緒上,由它負責SocketChannel的讀寫和編解碼工作。Acceptor執行緒池僅僅只用於客戶端的登陸、握手和安全認證,一旦鏈路建立成功,就將鏈路註冊到後端subReactor執行緒池的IO執行緒上,由IO執行緒負責後續的IO操作。

主從多執行緒模型如下圖所示:

簡單瞭解Java Netty Reactor三種執行緒模型

主從多執行緒模型

利用主從NIO執行緒模型,可以解決1個服務端監聽執行緒無法有效處理所有客戶端連線的效能不足問題。

它的工作流程總結如下:

1)從主執行緒池中隨機選擇一個Reactor執行緒作為Acceptor執行緒,用於繫結監聽埠,接收客戶端連線;Acceptor執行緒接收客戶端連線請求之後建立新的SocketChannel,將其註冊到主執行緒池的其它Reactor執行緒上,由其負責接入認證、IP黑白名單過濾、握手等操作;

2)步驟2完成之後,業務層的鏈路正式建立,將SocketChannel從主執行緒池的Reactor執行緒的多路複用器上摘除,重新註冊到Sub執行緒池的執行緒上,用於處理I/O的讀寫操作。

2. Netty執行緒模型

2.1. Netty執行緒模型分類

事實上,Netty的執行緒模型與1.2章節中介紹的三種Reactor執行緒模型相似,下面章節我們通過Netty服務端和客戶端的執行緒處理流程圖來介紹Netty的執行緒模型。

2.1.1. 服務端執行緒模型
一種比較流行的做法是服務端監聽執行緒和IO執行緒分離,類似於Reactor的多執行緒模型,它的工作原理圖如下:

簡單瞭解Java Netty Reactor三種執行緒模型

2.1.2. 客戶端執行緒模型

相比於服務端,客戶端的執行緒模型簡單一些,它的工作原理如下:

簡單瞭解Java Netty Reactor三種執行緒模型

2.2. Reactor執行緒NioEventLoop

NioEventLoop是Netty的Reactor執行緒,它的職責如下:

  • 作為服務端Acceptor執行緒,負責處理客戶端的請求接入;
  • 作為客戶端Connecor執行緒,負責註冊監聽連線操作位,用於判斷非同步連線結果;
  • 作為IO執行緒,監聽網路讀操作位,負責從SocketChannel中讀取報文;
  • 作為IO執行緒,負責向SocketChannel寫入報文傳送給對方,如果發生寫半包,會自動註冊監聽寫事件,用於後續繼續傳送半包資料,直到資料全部發送完成;
  • 作為定時任務執行緒,可以執行定時任務,例如鏈路空閒檢測和傳送心跳訊息等;
  • 作為執行緒執行器可以執行普通的任務執行緒(Runnable)。

2.3. NioEventLoop設計原理

我們知道當系統在執行過程中,如果頻繁的進行執行緒上下文切換,會帶來額外的效能損耗。多執行緒併發執行某個業務流程,業務開發者還需要時刻對執行緒安全保持警惕,哪些資料可能會被併發修改,如何保護?這不僅降低了開發效率,也會帶來額外的效能損耗。

序列執行Handler鏈

為了解決上述問題,Netty採用了序列化設計理念,從訊息的讀取、編碼以及後續Handler的執行,始終都由IO執行緒NioEventLoop負責,這就意外著整個流程不會進行執行緒上下文的切換,資料也不會面臨被併發修改的風險,對於使用者而言,甚至不需要了解Netty的執行緒細節,這確實是個非常好的設計理念,它的工作原理圖如下:

簡單瞭解Java Netty Reactor三種執行緒模型

一個NioEventLoop聚合了一個多路複用器Selector,因此可以處理成百上千的客戶端連線,Netty的處理策略是每當有一個新的客戶端接入,則從NioEventLoop執行緒組中順序獲取一個可用的NioEventLoop,當到達陣列上限之後,重新返回到0,通過這種方式,可以基本保證各個NioEventLoop的負載均衡。一個客戶端連線只註冊到一個NioEventLoop上,這樣就避免了多個IO執行緒去併發操作它。

Netty通過序列化設計理念降低了使用者的開發難度,提升了處理效能。利用執行緒組實現了多個序列化執行緒水平並行執行,執行緒之間並沒有交集,這樣既可以充分利用多核提升並行處理能力,同時避免了執行緒上下文的切換和併發保護帶來的額外效能損耗。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。