部落格名稱: pjmike的部落格
前言
我們在使用Netty進行服務端開發的時候,一般來說會定義兩個NioEventLoopGroup線程池,一個"bossGroup"線程池去負責處理用戶端連接配接,一個"workGroup"線程池去負責處理讀寫操作。那麼,我們為什麼要這麼做呢?這樣做的好處是什麼呢?能不能隻使用一個NioEventLoopGroup呢?這就是我們今天要讨論的主題——Netty的線程模型
Reactor線程模型
實際上Netty線程模型就是Reactor模式的一個實作,而Reactor模式又是什麼呢?
Reactor模式是基于事件驅動開發的,核心組成部分包括Reactor和線程池,其中Reactor負責監聽和配置設定事件,線程池負責處理事件,而根據Reactor的數量和線程池的數量,又将Reactor分為三種模型:
- 單線程模型 (單Reactor單線程)
- 多線程模型 (單Reactor多線程)
- 主從多線程模型 (多Reactor多線程)
單線程模型
圖檔摘自: http:// gee.cs.oswego.edu/dl/cp jslides/nio.pdf
- Reactor内部通過 selector 監控連接配接事件,收到事件後通過 dispatch 進行分發,如果是連接配接建立的事件,則由Acceptor處理, Acceptor 通過accept接受連接配接,并建立一個Handler來處理連接配接後續的各種事件,如果是讀寫事件,直接調用連接配接對應的 Handler 來處理
- Handler完成read->(decode->compute->encode)->send的業務流程
- 這種模型好處是簡單,壞處卻很明顯,當某個Handler阻塞時,會導緻其他用戶端的handler和accpetor都得不到執行,無法做到高性能,隻适用于業務處理非常快速的場景
多線程模型
圖檔摘自: http:// gee.cs.oswego.edu/dl/cp jslides/nio.pdf
- 主線程中,Reactor對象通過selector監控連接配接事件,收到事件後通過dispatch進行分發,如果是連接配接建立事件,則由Acceptor處理,Acceptor通過accept接收連接配接,并建立一個Handler來處理後續事件,而Handler隻負責響應事件,不進行業務操作,也就是隻進行read讀取資料和write寫出資料,業務處理交給一個線程池進行處理
- 線程池配置設定一個線程完成真正的業務處理,然後将響應結果交給主程序的Handler處理,Handler将結果send給client (下面是核心代碼)
單Reactor承當所有事件的監聽和響應,而當我們的服務端遇到大量的用戶端同時進行連接配接,或者在請求連接配接時執行一些耗時操作,比如身份認證,權限檢查等,這種瞬時的高并發就容易成為性能瓶頸
主從多線程模型 (最流行)
圖檔摘自: http:// gee.cs.oswego.edu/dl/cp jslides/nio.pdf
- 存在多個Reactor, 每個Reactor都有自己的selector選擇器 ,線程和dispatch
- 主線程中的mainReactor通過自己的selector監控連接配接建立事件,收到事件後通過Accpetor接收,将新的連接配接配置設定給某個子線程
- 子線程中的subReactor将mainReactor配置設定的連接配接加入連接配接隊列中通過自己的selector進行監聽,并建立一個Handler用于處理後續事件
- Handler完成read->業務處理->send的完整業務流程
Netty中的線程模型與Reactor的聯系
Netty主要靠NioEventLoopGroup線程池來實作具體的線程模型的
單線程模型
單線程模型就是隻指定一個線程執行用戶端連接配接和讀寫操作,也就是在一個Reactor中完成,對應在Netty中的實作就是将NioEventLoopGroup線程數設定為1,核心代碼是:
NioEventLoopGroup group = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ServerHandlerInitializer());
它的工作流程大緻如下:
上述單線程模型就對應了Reactor的單線程模型
多線程模型
多線程模型就是在一個單Reactor中進行用戶端連接配接處理,然後業務處理交給線程池,核心代碼如下:
NioEventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ServerHandlerInitializer());
走進group方法可以發現我們平時設定的bossGroup和workerGroup就是使用了同一個group
@Override
public ServerBootstrap group(EventLoopGroup group) {
return group(group, group);
}
工作流程如下:
主從多線程模型 (最常使用)
主從多線程模型是有多個Reactor,也就是存在多個selector,是以我們定義一個bossGroup和一個workGroup,核心代碼如下:
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ServerHandlerInitializer());
工作流程如下:
注意:其實在Netty中,bossGroup線程池最終還是隻會随機選擇一個線程用于處理用戶端連接配接,與此同時,NioServerSocetChannel綁定到bossGroup的線程中,NioSocketChannel綁定到workGroup的線程中小結
以上總結了Reactor的三種模型以及Netty中的對應實作,在Netty中,我們使用的最多的還是主從多線程模型。關于Reactor的學習,最權威的資料應該是Doug Lea大神的Scalable IO in Java,有興趣的同學可以看看
參考資料
- http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
- https://time.geekbang.org/column/article/8805
- https://segmentfault.com/a/1190000007403873
- https://www.infoq.cn/article/netty-threading-model