1. 程式人生 > >netty學習之Reactor線程模型以及在netty中的應用

netty學習之Reactor線程模型以及在netty中的應用

rec 直接 滿足 red 轉載 chan tail io處理 理論

轉載:http://blog.csdn.net/u010853261/article/details/55805216

說道netty的線程模型,我們第一反應就是經典的Reactor線程模型,下面我們就來一起探討一下三種經典的Reactor線程模型:

這裏我們需要理解的一點是Reactor線程模型是基於同步非阻塞IO實現的。對於異步非阻塞IO的實現是Proactor模型。

本文主要包括
(1)Reactor單線程模型

(2)Reactor多線程模型

(3)主從Reactor多線程模型

(4)netty的多線程模型

1.Reactor單線程模型

Reactor單線程模型就是指所有的IO操作都在同一個NIO線程上面完成的,也就是IO處理線程是單線程的。NIO線程的職責是:
(1)作為NIO服務端,接收客戶端的TCP連接;

(2)作為NIO客戶端,向服務端發起TCP連接;

(3)讀取通信對端的請求或則應答消息;

(4)向通信對端發送消息請求或則應答消息。

Reactor單線程模型圖如下所示:
技術分享

Reactor模式使用的是同步非阻塞IO(NIO),所有的IO操作都不會導致阻塞,理論上一個線程可以獨立的處理所有的IO操作(selector會主動去輪詢哪些IO操作就緒)。從架構層次看,一個NIO線程確實可以完成其承擔的職責,比如上圖的Acceptor類接收客戶端的TCP請求消息,當鏈路建立成功之後,通過Dispatch將對應的ByteBuffer轉發到指定的handler上,進行消息的處理。

對於一些小容量的應用場景下,可以使用單線程模型,但是對於高負載、大並發的應用場景卻不適合,主要原因如下:
(1)一個NIO線程處理成千上萬的鏈路,性能無法支撐,即使CPU的負荷達到100%;

(2)當NIO線程負載過重,處理性能就會變慢,導致大量客戶端連接超時然後重發請求,導致更多堆積未處理的請求,成為性能瓶頸。

(3)可靠性低,只有一個NIO線程,萬一線程假死或則進入死循環,就完全不可用了,這是不能接受的。

基於上訴問題,提出了Reactor的多線程模型:

2.Reactor多線程模型

Reactor多線程模型與單線程模型最大的區別在於,IO處理線程不再是一個線程,而是一組NIO處理線程。原理如下圖所示:
技術分享

Reactor多線程模型的特點如下:
(1)有一個專門的NIO線程—-Acceptor線程用於監聽服務端,接收客戶端的TCP連接請求。

(2)網絡IO操作—-讀寫等操作由一個專門的線程池負責,線程池可以使用JDK標準的線程池實現,包含一個任務隊列和N個可用的線程,這些NIO線程就負責讀取、解碼、編碼、發送。

(3)一個NIO線程可以同時處理N個鏈路,但是一個鏈路只對應一個NIO線程。

Reactor多線程模型可以滿足絕大多數的場景,除了一些個別的特殊場景:比如一個NIO線程負責處理客戶所有的連接請求,但是如果連接請求中包含認證的需求(安全認證),在百萬級別的場景下,就存在性能問題了,因為認證本身就要消耗CPU,為了解決這種情景下的性能問題,產生了第三種線程模型:Reactor主從線程模型。

3.主從Reactor多線程模型

主從Reactor線程模型的特點是:服務端用於接收客戶端連接的不再是一個單獨的NIO線程,而是一個獨立的NIO的線程池。Acceptor接收到客戶端TCP連接請求並處理完成後(可能包含接入認證),再將新創建的SocketChannel註冊到IO線程池(sub reactor)的某個IO處理線程上並處理編解碼和讀寫工作。Acceptor線程池僅負責客戶端的連接與認證,一旦鏈路連接成功,就將鏈路註冊到後端的sub Reactor的IO線程池中。 線程模型圖如下:
技術分享

利用主從Reactor模型可以解決服務端監聽線程無法有效處理所有客戶連接的性能不足問題,這也是netty推薦使用的線程模型。

4. netty的線程模型

netty的線程模型是可以通過設置啟動類的參數來配置的,設置不同的啟動參數,netty支持Reactor單線程模型、多線程模型和主從Reactor多線程模型。
技術分享

服務端啟動時創建了兩個NioEventLoopGroup,一個是boss,一個是worker。實際上他們是兩個獨立的Reactor線程池,一個用於接收客戶端的TCP連接,另一個用於處理Io相關的讀寫操作,或則執行系統的Task,定時Task。

Boss線程池職責如下:
(1)接收客戶端的連接,初始化Channel參數
(2)將鏈路狀態變更時間通知給ChannelPipeline

worker線程池作用是:
(1)異步讀取通信對端的數據報,發送讀事件到ChannelPipeline
(2)異步發送消息到通信對端,調用ChannelPipeline的消息發送接口
(3)執行系統調用Task;
(4)執行定時任務Task;

通過配置boss和worker線程池的線程個數以及是否共享線程池等方式,netty的線程模型可以在單線程、多線程、主從線程之間切換。

為了提升性能,netty在很多地方都進行了無鎖設計。比如在IO線程內部進行串行操作,避免多線程競爭造成的性能問題。表面上似乎串行化設計似乎CPU利用率不高,但是通過調整NIO線程池的線程參數,可以同時啟動多個串行化的線程並行運行,這種局部無鎖串行線程設計性能更優。

nettyd的NioEventLoop讀取到消息之後,直接調用ChannelPipeline的fireChannelRead(Object msg),只要用戶不主動切換線程,一直都是由NioEventLoop調用用用戶的Handler,期間不進行線程切換,這種串行化設計避免了多線程操作導致的鎖競爭,性能角度看是最優的。

5. netty的線程模型 設置最佳實踐

(1)創建兩個NioEventLoopGroup,隔離NIO Acceptor和NIO的IO線程。

(2)盡量不要在ChannelHandler中啟動用戶線程(解碼之後,將POJO消息派發到後端的業務線程池除外)。

(3)解碼要放在NIO線程調用的Handler中,不要放在用戶線程中解碼。

(4)如果IO操作非常簡單,不涉及復雜的業務邏輯計算,沒有可能導致阻塞的磁盤操作、數據庫操作、網絡操作等,可以再NIO線程調用的Handler中完成業務邏輯,不要切換到用戶線程。

(5)如果IO業務操作比較復雜,就不要在NIO線程上完成,因為阻塞可能會導致NIO線程假死,嚴重降低性能。這時候可以把POJO封裝成Task,派發到業務線程池中由業務線程處理,以保證NIO,線程被盡快的釋放,處理其余的IO操作。

netty學習之Reactor線程模型以及在netty中的應用