Reactor單線程模型就是指全部的IO操做都在同一個NIO線程上面完成的,也就是IO處理線程是單線程的。NIO線程的職責是:
(1)做爲NIO服務端,接收客戶端的TCP鏈接;react
(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的多線程模型:oop
Reactor多線程模型與單線程模型最大的區別在於,IO處理線程再也不是一個線程,而是一組NIO處理線程。原理以下圖所示:
Reactor多線程模型的特色以下:
(1)有一個專門的NIO線程—-Acceptor線程用於監聽服務端,接收客戶端的TCP鏈接請求。
(2)網絡IO操做—-讀寫等操做由一個專門的線程池負責,線程池可使用JDK標準的線程池實現,包含一個任務隊列和N個可用的線程,這些NIO線程就負責讀取、解碼、編碼、發送。
(3)一個NIO線程能夠同時處理N個鏈路,可是一個鏈路只對應一個NIO線程。
Reactor多線程模型能夠知足絕大多數的場景,除了一些個別的特殊場景:好比一個NIO線程負責處理客戶全部的鏈接請求,可是若是鏈接請求中包含認證的需求(安全認證),在百萬級別的場景下,就存在性能問題了,由於認證自己就要消耗CPU,爲了解決這種情景下的性能問題,產生了第三種線程模型:Reactor主從線程模型。
主從Reactor線程模型的特色是:服務端用於接收客戶端鏈接的再也不是一個單獨的NIO線程,而是一個獨立的NIO的線程池。Acceptor接收到客戶端TCP鏈接請求並處理完成後(可能包含接入認證),再將新建立的SocketChannel註冊到IO線程池(sub reactor)的某個IO處理線程上並處理編解碼和讀寫工做。Acceptor線程池僅負責客戶端的鏈接與認證,一旦鏈路鏈接成功,就將鏈路註冊到後端的sub Reactor的IO線程池中。 線程模型圖以下:
利用主從Reactor模型能夠解決服務端監聽線程沒法有效處理全部客戶鏈接的性能不足問題,這也是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,期間不進行線程切換,這種串行化設計避免了多線程操做致使的鎖競爭,性能角度看是最優的。
(1)建立兩個NioEventLoopGroup,隔離NIO Acceptor和NIO的IO線程。
(2)儘可能不要在ChannelHandler中啓動用戶線程(解碼以後,將POJO消息派發到後端的業務線程池除外)。
(3)解碼要放在NIO線程調用的Handler中,不要放在用戶線程中解碼。
(4)若是IO操做很是簡單,不涉及複雜的業務邏輯計算,沒有可能致使阻塞的磁盤操做、數據庫操做、網絡操做等,能夠再NIO線程調用的Handler中完成業務邏輯,不要切換到用戶線程。 (5)若是IO業務操做比較複雜,就不要在NIO線程上完成,由於阻塞可能會致使NIO線程假死,嚴重下降性能。這時候能夠把POJO封裝成Task,派發到業務線程池中由業務線程處理,以保證NIO,線程被儘快的釋放,處理其他的IO操做。