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 执行自定义的异步任务和定时任务。
- 还没有人评论,欢迎说说您的想法!