亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Netty 線程模型

1. 前言

前面幾節分別講解了 Reactor 的三種線程模型,都知道主從 Reactor 多線程模型的性能非常的好,那么 Netty 是否就是使用主從 Reactor 多線程模型呢?其實 Netty 線程模型是基于主從 Reactor 多線程模型做了一定的改造,Netty 的線程模型要比 Reactor 主從多線程模型還要復雜。本節主要是通過圖解的方式逐步分析 Netty 線程模型的原理。

2. Netty 模型介紹

2.1 模型介紹

圖片描述

Netty 模型架構說明:

  1. Netty 抽象出兩個線程池,分別是 BossGroup 和 WorkerGroup,BossGroup 專門負責接受客戶端的連接,Worker 請求處理;
  2. BossGroup 和 WorkerGroup 類型默認使用的是 NioEventLoopGroup;
  3. NioEventLoopGroup 是一個定時任務線程池,NioEventLoop 是真正工作的線程;
  4. 每個 BossGroup 的 NioEventLoop 分別循環執行三個步驟
    4.1 每個 NioEventLoop 都有一個 Selector,并且不斷輪詢 accept 事件;
    4.2 處理 accept 事件,與客戶端建立連接,生成 NioSocketChannel,并且將其注冊到某個 WorkerGroup 下的 NioEventLoop 上的 Selector 上;
    4.3 處理任務隊列中的任務,即 runAllTasks。
  5. 每個 WorkerGroup 的 NioEventLoop 分別循環執行三個步驟
    5.1 輪詢 read 和 write 事件;
    5.2 處理 I/O 事件,即 read,write 事件,并在其對應的 NioSocketChannel 處理;
    5.3 處理任務隊列的任務,即 runAllTasks。

2.2 核心概念理解

Tips: 額外知識點補充,這里提前劇透一下 Channel、ChannelPipeline、ChannelHanlder 之間的關系

  1. 每個客戶端連接進來的時候,服務端都會建立一個 Channel;
  2. 為每個 Channel 綁定一個 NioEventLoop 線程,該線程主要負責處理該 Channel 的業務,一個 Channel 對應一個 NioEventLoop,但是一個 NioEventLoop 可以同時服務多個 Channel;
  3. 為每個 Channel 綁定一個 ChannelPipeline,它是一個業務管道,專門負責管理業務鏈,也就是 ChannelHandler;
  4. WorkerGroup 的核心方法是 runAllTasks (),它主要是觸發 NioEventLoop 去處理對應的 Channel 里面的 ChannelPipeline 里面的 ChannelHandler 里面的業務邏輯。

3. Netty 模型配置

3.1 單線程配置

在 ServerBootstrap 調用方法 group 的時候,傳遞的參數是同一個線程組,且在構造線程組的時候,構造參數為 1。

實例:

public class ServerNetty{
    private ServerBootstrap bootstrap=null;
    private EventLoopGroup group=null;
    public void init(){
        group=new NioEventLoopGroup(1);//線程數量為 1
        bootstrap.group(group,group);
    }
}

3.2 多線程配置

在 ServerBootstrap 調用方法 group 的時候,傳遞的參數是兩個不同的線程組,負責監聽的 acceptor 線程組的線程數為 1,負責處理客戶端線程組的線程數大于 1。

實例:

public class ServerNetty{
    private ServerBootstrap bootstrap=null;
    private EventLoopGroup acceptorGroup=null;
    private EventLoopGroup clientGroup=null;
    public void init(){
        acceptorGroup=new NioEventLoopGroup(1);//線程數量為 1
        clientGroup=new NioEventLoopGroup();//默認是 cpu 的核心數
        bootstrap.group(acceptorGroup,clientGroup);
    }
}

3.3 主從多線程配置

在 ServerBootstrap 調用方法 group 的時候,傳遞的參數是兩個不同的線程組,負責監聽的 acceptor 線程組的線程數大于 1,負責處理客戶端線程組的線程數大于 1。

實例:

public class ServerNetty{
    private ServerBootstrap bootstrap=null;
    private EventLoopGroup acceptorGroup=null;
    private EventLoopGroup clientGroup=null;
    public void init(){
        acceptorGroup=new NioEventLoopGroup();//默認是 cpu 的核心數
        clientGroup=new NioEventLoopGroup();//默認是 cpu 的核心數
        bootstrap.group(acceptorGroup,clientGroup);
    }
}

4. 自定義任務隊列

通常情況下,任務隊列中常見的任務主要有以下幾種類型:

  1. 用戶自定義的異步任務,比如:依賴線程池去異步某個任務等;
  2. 用戶自定義的定時任務,比如:依賴定時線程池去定義每隔 n 秒執行某個任務等;
  3. 非當前 reactor 線程調用 channel 的各種方法。

4.1 異步任務

其實跟我們平時使用線程池沒有什么區別,只不過調用的是底層 Netty 線程組。

實例:

//使用 reactor 線程的異步任務
ctx.channel().eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        //...
    }
});

//使用線程池去實現異步任務
ExecutorService es = Executors.newFixedThreadPool(5);
es.execute(new Runnable() {
    @Override
    public void run() {
        
    }
});

4.2 定時任務

其實類似我們平時使用的定時任務線程池(如:ScheduledThreadPool),只不過是調用底層 Netty 線程組。

實例:

//使用 reactor 線程實現的定時任務
ctx.channel().eventLoop().schedule(new Runnable() {
    @Override
    public void run() {

    }
}, 60, TimeUnit.SECONDS);

//使用線程池去實現定時任務
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
ses.schedule(new Runnable() {
    public void run() {
        System.out.println("i:" + temp);
    }
}, 3, TimeUnit.SECONDS);

總結:

  1. 當前 reactor 線程調用當前 eventLoop 執行任務,直接執行,否則,添加到任務隊列稍后執行;
  2. netty 內部的任務分為普通任務和定時任務,分別落地到 MpscQueue 和 PriorityQueue;
  3. netty 每次執行任務循環之前,會將已經到期的定時任務從 PriorityQueue 轉移到 MpscQueue;
  4. netty 每隔 64 個任務檢查一下是否該退出任務循環。

5. 小結

本節主要掌握的核心知識點

  1. Netty 的模型的理解,以及每個 NioEventLoop 所執行的三個核心操作,分別是①輪詢出 IO 事件;②處理 IO 事件;③處理任務隊列;
  2. 了解 Channel、ChannelPipeline、ChannelHandler 之間的關系,以及 NioEventLoop 主要負責處理每個 Channel 的業務邏輯;
  3. Netty 如何配置三種 Reactor 模型;
  4. 如何使用內置的 NioEventLoop 執行自定義的異步任務和定時任務。