淺談Netty的執行緒模型

微笑面對生活發表於2018-10-04

說起netty的執行緒模型,首先我們應該能想到經典的Reactor執行緒模型,不同的NIO框架的實現方式儘管不同,但是本質上還是遵循了Reactor執行緒模型。

1. 前言

所以我們先大致看看Reactor執行緒模型:

  1. Reactor單執行緒模型:所有的IO操作都由同一個NIO執行緒處理。

使用的是非同步非阻塞IO,所有的IO操作都不會導致阻塞,理論上一個執行緒可以獨立處理所有IO相關的操作。但是即使執行緒利用率達到100%,也不會能撐住太高的併發。

  1. Reactor多執行緒模型:Reactor多執行緒模型與單執行緒模型最大的區別就是有一組NIO執行緒來處理IO操作。

有專門一個NIO執行緒—-Acceptor執行緒用於監聽服務端,接收客戶端的TCP連線請求,網路IO操作—-讀、寫等由一個NIO執行緒池負責。瓶頸在於客戶端同時連線數,畢竟是一個執行緒再做監聽工作。

  1. 主從Reactor模型:服務端用於接收客戶端連線的不再是一個單獨的NIO執行緒,而是一個獨立的NIO執行緒池。後續操作與2類似。

官方推薦模型,可以解決一個服務端監聽執行緒無法有效處理所有客戶端連線的效能不足問題。

2. Netty執行緒模型

Netty的執行緒模型並不是一成不變的,它實際取決於使用者的啟動引數配置。通過設定不同的啟動引數,Netty可以同時支援Reactor但單執行緒模型、多執行緒模型和主從Reactor多執行緒模型。

Demo
 // 配置服務端的NIO執行緒組
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();

                // 設定執行緒組及Socket引數
                b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        .childHandler(new ChildChannelHandler());
複製程式碼

服務端啟動的時候,建立了兩個NioEventLoopGroup。它們實際是兩個獨立的Reactor執行緒池。一個用於接收客戶端的TCP連線,另一個用於處理IO相關的讀寫操作,或者執行系統Task、定時任務等。

Netty用於接收客戶端請求的執行緒池職責如下:
  1. 接收客戶端TCP連線,初始化Channel引數;
  2. 將鏈路狀態變更事件通知給ChannelPipeline。
Netty處理IO操作的Reactor執行緒池職責如下:
  1. 非同步讀取通訊對端的資料包,傳送讀事件到ChannalPipeline;
  2. 非同步傳送訊息到通訊對端,呼叫ChannelPipeline的訊息傳送介面;
  3. 執行系統呼叫task;
  4. 執行定時任務Task,例如鏈路空閒狀態監測定時任務。

通過調整執行緒池的執行緒個數、是否共享執行緒池等方式,Netty的Reactor模型可以在上述模型中靈活轉換。

3. 與Reactor類似卻又不同

為了儘可能的提升效能,Netty在很多地方進行了無鎖化設計,例如在IO執行緒內部進行序列操作,避免多執行緒競爭導致的效能下降問題。表面上看,序列化設計似乎CPU利用率不高,併發程度不夠,但是,通過調整NIO執行緒池的執行緒引數,可以同時啟動多個序列化的執行緒並行執行,這種區域性無鎖化的序列執行緒設計相比一個佇列---多個工作執行緒的模型效能更優。

Netty的NioEventLoop讀取到訊息之後,直接呼叫ChannelPipeline的fireChannelRead(Object msg)。只要使用者不主動切換執行緒,一直都是由NioEventLoopLoop呼叫使用者的Handler,期間不進行執行緒切換。這種序列處理方式避免了多執行緒操作導致的鎖的競爭,從效能角度看是最優的。

相關文章