Netty 線程模型
1. 前言
前面幾節分別講解了 Reactor 的三種線程模型,都知道主從 Reactor 多線程模型的性能非常的好,那么 Netty 是否就是使用主從 Reactor 多線程模型呢?其實 Netty 線程模型是基于主從 Reactor 多線程模型做了一定的改造,Netty 的線程模型要比 Reactor 主從多線程模型還要復雜。本節主要是通過圖解的方式逐步分析 Netty 線程模型的原理。
2. Netty 模型介紹
2.1 模型介紹
Netty 模型架構說明:
- Netty 抽象出兩個線程池,分別是 BossGroup 和 WorkerGroup,BossGroup 專門負責接受客戶端的連接,Worker 請求處理;
- BossGroup 和 WorkerGroup 類型默認使用的是 NioEventLoopGroup;
- NioEventLoopGroup 是一個定時任務線程池,NioEventLoop 是真正工作的線程;
- 每個 BossGroup 的 NioEventLoop 分別循環執行三個步驟
4.1 每個 NioEventLoop 都有一個 Selector,并且不斷輪詢 accept 事件;
4.2 處理 accept 事件,與客戶端建立連接,生成 NioSocketChannel,并且將其注冊到某個 WorkerGroup 下的 NioEventLoop 上的 Selector 上;
4.3 處理任務隊列中的任務,即 runAllTasks。 - 每個 WorkerGroup 的 NioEventLoop 分別循環執行三個步驟
5.1 輪詢 read 和 write 事件;
5.2 處理 I/O 事件,即 read,write 事件,并在其對應的 NioSocketChannel 處理;
5.3 處理任務隊列的任務,即 runAllTasks。
2.2 核心概念理解
Tips: 額外知識點補充,這里提前劇透一下 Channel、ChannelPipeline、ChannelHanlder 之間的關系
- 每個客戶端連接進來的時候,服務端都會建立一個 Channel;
- 為每個 Channel 綁定一個 NioEventLoop 線程,該線程主要負責處理該 Channel 的業務,一個 Channel 對應一個 NioEventLoop,但是一個 NioEventLoop 可以同時服務多個 Channel;
- 為每個 Channel 綁定一個 ChannelPipeline,它是一個業務管道,專門負責管理業務鏈,也就是 ChannelHandler;
- 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. 自定義任務隊列
通常情況下,任務隊列中常見的任務主要有以下幾種類型:
- 用戶自定義的異步任務,比如:依賴線程池去異步某個任務等;
- 用戶自定義的定時任務,比如:依賴定時線程池去定義每隔 n 秒執行某個任務等;
- 非當前 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);
總結:
- 當前 reactor 線程調用當前 eventLoop 執行任務,直接執行,否則,添加到任務隊列稍后執行;
- netty 內部的任務分為普通任務和定時任務,分別落地到 MpscQueue 和 PriorityQueue;
- netty 每次執行任務循環之前,會將已經到期的定時任務從 PriorityQueue 轉移到 MpscQueue;
- netty 每隔 64 個任務檢查一下是否該退出任務循環。
5. 小結
本節主要掌握的核心知識點
- Netty 的模型的理解,以及每個 NioEventLoop 所執行的三個核心操作,分別是①輪詢出 IO 事件;②處理 IO 事件;③處理任務隊列;
- 了解 Channel、ChannelPipeline、ChannelHandler 之間的關系,以及 NioEventLoop 主要負責處理每個 Channel 的業務邏輯;
- Netty 如何配置三種 Reactor 模型;
- 如何使用內置的 NioEventLoop 執行自定義的異步任務和定時任務。