Netty 線程模型
1. 前言
前面幾節(jié)分別講解了 Reactor 的三種線程模型,都知道主從 Reactor 多線程模型的性能非常的好,那么 Netty 是否就是使用主從 Reactor 多線程模型呢?其實(shí) Netty 線程模型是基于主從 Reactor 多線程模型做了一定的改造,Netty 的線程模型要比 Reactor 主從多線程模型還要復(fù)雜。本節(jié)主要是通過圖解的方式逐步分析 Netty 線程模型的原理。
2. Netty 模型介紹
2.1 模型介紹
Netty 模型架構(gòu)說明:
- Netty 抽象出兩個(gè)線程池,分別是 BossGroup 和 WorkerGroup,BossGroup 專門負(fù)責(zé)接受客戶端的連接,Worker 請(qǐng)求處理;
- BossGroup 和 WorkerGroup 類型默認(rèn)使用的是 NioEventLoopGroup;
- NioEventLoopGroup 是一個(gè)定時(shí)任務(wù)線程池,NioEventLoop 是真正工作的線程;
- 每個(gè) BossGroup 的 NioEventLoop 分別循環(huán)執(zhí)行三個(gè)步驟
4.1 每個(gè) NioEventLoop 都有一個(gè) Selector,并且不斷輪詢 accept 事件;
4.2 處理 accept 事件,與客戶端建立連接,生成 NioSocketChannel,并且將其注冊(cè)到某個(gè) WorkerGroup 下的 NioEventLoop 上的 Selector 上;
4.3 處理任務(wù)隊(duì)列中的任務(wù),即 runAllTasks。 - 每個(gè) WorkerGroup 的 NioEventLoop 分別循環(huán)執(zhí)行三個(gè)步驟
5.1 輪詢 read 和 write 事件;
5.2 處理 I/O 事件,即 read,write 事件,并在其對(duì)應(yīng)的 NioSocketChannel 處理;
5.3 處理任務(wù)隊(duì)列的任務(wù),即 runAllTasks。
2.2 核心概念理解
Tips: 額外知識(shí)點(diǎn)補(bǔ)充,這里提前劇透一下 Channel、ChannelPipeline、ChannelHanlder 之間的關(guān)系
- 每個(gè)客戶端連接進(jìn)來的時(shí)候,服務(wù)端都會(huì)建立一個(gè) Channel;
- 為每個(gè) Channel 綁定一個(gè) NioEventLoop 線程,該線程主要負(fù)責(zé)處理該 Channel 的業(yè)務(wù),一個(gè) Channel 對(duì)應(yīng)一個(gè) NioEventLoop,但是一個(gè) NioEventLoop 可以同時(shí)服務(wù)多個(gè) Channel;
- 為每個(gè) Channel 綁定一個(gè) ChannelPipeline,它是一個(gè)業(yè)務(wù)管道,專門負(fù)責(zé)管理業(yè)務(wù)鏈,也就是 ChannelHandler;
- WorkerGroup 的核心方法是 runAllTasks (),它主要是觸發(fā) NioEventLoop 去處理對(duì)應(yīng)的 Channel 里面的 ChannelPipeline 里面的 ChannelHandler 里面的業(yè)務(wù)邏輯。
3. Netty 模型配置
3.1 單線程配置
在 ServerBootstrap 調(diào)用方法 group 的時(shí)候,傳遞的參數(shù)是同一個(gè)線程組,且在構(gòu)造線程組的時(shí)候,構(gòu)造參數(shù)為 1。
實(shí)例:
public class ServerNetty{
private ServerBootstrap bootstrap=null;
private EventLoopGroup group=null;
public void init(){
group=new NioEventLoopGroup(1);//線程數(shù)量為 1
bootstrap.group(group,group);
}
}
3.2 多線程配置
在 ServerBootstrap 調(diào)用方法 group 的時(shí)候,傳遞的參數(shù)是兩個(gè)不同的線程組,負(fù)責(zé)監(jiān)聽的 acceptor 線程組的線程數(shù)為 1,負(fù)責(zé)處理客戶端線程組的線程數(shù)大于 1。
實(shí)例:
public class ServerNetty{
private ServerBootstrap bootstrap=null;
private EventLoopGroup acceptorGroup=null;
private EventLoopGroup clientGroup=null;
public void init(){
acceptorGroup=new NioEventLoopGroup(1);//線程數(shù)量為 1
clientGroup=new NioEventLoopGroup();//默認(rèn)是 cpu 的核心數(shù)
bootstrap.group(acceptorGroup,clientGroup);
}
}
3.3 主從多線程配置
在 ServerBootstrap 調(diào)用方法 group 的時(shí)候,傳遞的參數(shù)是兩個(gè)不同的線程組,負(fù)責(zé)監(jiān)聽的 acceptor 線程組的線程數(shù)大于 1,負(fù)責(zé)處理客戶端線程組的線程數(shù)大于 1。
實(shí)例:
public class ServerNetty{
private ServerBootstrap bootstrap=null;
private EventLoopGroup acceptorGroup=null;
private EventLoopGroup clientGroup=null;
public void init(){
acceptorGroup=new NioEventLoopGroup();//默認(rèn)是 cpu 的核心數(shù)
clientGroup=new NioEventLoopGroup();//默認(rèn)是 cpu 的核心數(shù)
bootstrap.group(acceptorGroup,clientGroup);
}
}
4. 自定義任務(wù)隊(duì)列
通常情況下,任務(wù)隊(duì)列中常見的任務(wù)主要有以下幾種類型:
- 用戶自定義的異步任務(wù),比如:依賴線程池去異步某個(gè)任務(wù)等;
- 用戶自定義的定時(shí)任務(wù),比如:依賴定時(shí)線程池去定義每隔 n 秒執(zhí)行某個(gè)任務(wù)等;
- 非當(dāng)前 reactor 線程調(diào)用 channel 的各種方法。
4.1 異步任務(wù)
其實(shí)跟我們平時(shí)使用線程池沒有什么區(qū)別,只不過調(diào)用的是底層 Netty 線程組。
實(shí)例:
//使用 reactor 線程的異步任務(wù)
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
//...
}
});
//使用線程池去實(shí)現(xiàn)異步任務(wù)
ExecutorService es = Executors.newFixedThreadPool(5);
es.execute(new Runnable() {
@Override
public void run() {
}
});
4.2 定時(shí)任務(wù)
其實(shí)類似我們平時(shí)使用的定時(shí)任務(wù)線程池(如:ScheduledThreadPool),只不過是調(diào)用底層 Netty 線程組。
實(shí)例:
//使用 reactor 線程實(shí)現(xiàn)的定時(shí)任務(wù)
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
}
}, 60, TimeUnit.SECONDS);
//使用線程池去實(shí)現(xiàn)定時(shí)任務(wù)
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
ses.schedule(new Runnable() {
public void run() {
System.out.println("i:" + temp);
}
}, 3, TimeUnit.SECONDS);
總結(jié):
- 當(dāng)前 reactor 線程調(diào)用當(dāng)前 eventLoop 執(zhí)行任務(wù),直接執(zhí)行,否則,添加到任務(wù)隊(duì)列稍后執(zhí)行;
- netty 內(nèi)部的任務(wù)分為普通任務(wù)和定時(shí)任務(wù),分別落地到 MpscQueue 和 PriorityQueue;
- netty 每次執(zhí)行任務(wù)循環(huán)之前,會(huì)將已經(jīng)到期的定時(shí)任務(wù)從 PriorityQueue 轉(zhuǎn)移到 MpscQueue;
- netty 每隔 64 個(gè)任務(wù)檢查一下是否該退出任務(wù)循環(huán)。
5. 小結(jié)
本節(jié)主要掌握的核心知識(shí)點(diǎn)
- Netty 的模型的理解,以及每個(gè) NioEventLoop 所執(zhí)行的三個(gè)核心操作,分別是①輪詢出 IO 事件;②處理 IO 事件;③處理任務(wù)隊(duì)列;
- 了解 Channel、ChannelPipeline、ChannelHandler 之間的關(guān)系,以及 NioEventLoop 主要負(fù)責(zé)處理每個(gè) Channel 的業(yè)務(wù)邏輯;
- Netty 如何配置三種 Reactor 模型;
- 如何使用內(nèi)置的 NioEventLoop 執(zhí)行自定義的異步任務(wù)和定時(shí)任務(wù)。