Netty之所以成为高性能NIO框架,其精心设计的高效线程模型功不可没,Netty线程模型基于了一个著名的模式——Reactor模式。
Reactor模型
Reactor模型是一种经典的线程模型,也叫反应器模型,网上已经由很多对它的介绍,这里就不过多的介绍,只是简单介绍三种模型的特点。
单线程模型
Reactor单线程模型仅使用一个线程来处理所有的事情,包括服务端和客户端的连接,以及连接产生的的读写事件。这看似提高了线程的使用效率,但缺陷也显而易见。因为仅有一个线程在工作,如果这个线程某个环节出错导致无法正常执行任务了,那么整个系统就会停止工作,也就是系统会因为这个单线程模型而变得不可用,这在绝大部分场景(所有)下是不允许出现的。当然也还有其他问题,正是因为这些缺陷,单线程的Reactor模型使用的相对比较少。
多线程模型
针对单线程的问题,Reactor多线程模型应运而生。从上面的原理图可以发现,在多线程模型中,接收连接和处理请求作为两部分分离了,Acceptor由一个单独的线程处理,另外有一组NIO线程来负责处理IO操作。
在绝大多数的场景下,多线程模型都可以满足要求,但在某些高并发高负载等极限情况下,一个 NIO 线程负责监听和处理所有的客户端连接还是可能会存在性能问题。
主从多线程模型
为了解决上述问题,就产生了主从多线程模型。和多线程模型相比,它不再使用一个线程接收客户端连接,而是采用一个独立的NIO线程池。这个单独的Acceptor线程池仅负责客户端的连接与认证,一旦链路连接成功,就将链路注册到后端的NIO线程池中。这样就可以解决一个服务端监听线程无法有效处理所有客户连接的性能不足问题。
Netty线程模型实现
Netty的线程模型可以通过引导类中的参数来配置,通过设置不同的参数,它支持以上的三种线程模型,Netty推荐使用主从多线程模型,通过如下方式即可实现该配置。
NioEventLoopGroup parentGroup = new NioEventLoopGroup();
NioEventLoopGroup childGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup,childGroup);
在上面的代码中,创建了两个EventLoopGroup的实现类的对象(这里以NioEventLoopGroup为例),相当于创建了两个线程池,EventLoopGroup管理的线程数可以通过构造函数设置,如果没有设置,会首先从系统属性中获取 “io.netty.eventLoopThreads” 的值,如果没有设置,就赋值为系统处理器核心数*2。
parentGroup
负责处理客户端的TCP连接请求,如果服务端只有一个端口需要监听,建议将其线程数设置为1,相当于Reactor模型中的Acceptor线程池。
childGroup
负责I/O的读写操作,通过 ServerBootstrap 的 group 方法进行设置,用于后续产生的 Channel 绑定,相当于Reactor模型中的NIO线程池。
EventLoopGroup初始化
下面对Netty的线程池初始化进行分析,这里以NioEventLoopGroup为例,通过追踪代码,NioEventLoopGroup的构造方法会逐级向上最终调用到下面的父类构造器中:
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
// 去掉了部分参数检查等代码
threadFactory = newDefaultThreadFactory();
// 创建一个nThreads大小的SingleThreadEventExecutor数组用于储存EventLoop对象
children = new SingleThreadEventExecutor数组用于[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
// 调用NioEventLoopGroup.newChild(...)方法创建EventLoop对象,对数组元素进行赋值
children[i] = newChild(threadFactory, args);
success = true;
// 省略了部分异常处理代码
// 如果上面的赋值过程出现异常,则将之前创建EventLoop对象全部关闭
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
}
}
}
至此,NioEventLoopGroup的初始化过程就结束了。可以看到,MultithreadEventExecutorGroup内部的维护的EventExecutor数组children
是Netty的线程池的实现的基础。
EventLoop初始化
在NioEventLoopGroup初始化时,执行了如下代码对数组children的元素进行赋值:
children[i] = newChild(threadFactory, args);
该方法由NioEventLoopGroup自身实现,其内容如下:
protected EventExecutor newChild(
ThreadFactory threadFactory, Object... args) throws Exception {
return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
}
其实就是通过NioEventLoop的构造方法创建一个NioEventLoop实例对象并返回。
该构造方法同样逐级向上调用直到SingleThreadEventExecutor,在本类和父类均有部分实现:
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) {
super(parent, threadFactory, false);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
provider = selectorProvider;
selector = openSelector();
}
// SingleThreadEventExecutor构造方法
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
// 这里省略了参数检查和Runnable接口的实现代码
this.parent = parent;
this.addTaskWakesUp = addTaskWakesUp;
thread = threadFactory.newThread(new Runnable() {});
threadProperties = new DefaultThreadProperties(thread);
taskQueue = newTaskQueue();
}
从上面可以看出,在NioEventLoop的构造器中有一个名为selector的NioEventLoop类型字段,这个字段代表了和NioEventLoop关联的多路复用器selector,这里通过openSelector()对其赋值,暂时还没添加感兴趣事件。
再看SingleThreadEventExecutor构造器,发现里面有一个final
修饰的Thread类型的thread字段,通过newThread()方法创建了一个新的线程,并赋值给thread字段,该字段代表了与SingleThreadEventExecutor关联的本地线程,并且在其生命周期内, 绑定的线程都不会再改变。
下面通过一张图来展示EventLoop、EventLoopGroup、Thread以及Channel的关系。
![](https://i-blog.csdnimg.cn/blog_migrate/0bdfa3ac991777f45666a36856cfe4c1.png)
简单解读一下整个流程:
- 一个EventLoopGroup可以包含一个或者多个EventLoop;
- 一个EventLoop在它的生命周期内只和一个Thread绑定;
- 所有由EventLoop处理的I/O事件都由和它绑定的Thread处理;
- 一个Channel在生命周期内只注册于一个EventLoop;
- 一个EventLoop可以连接一个或者多个Channel。