1 Reactor模型内容补充

之前的这篇文章【25】说说Redis的非阻塞IO多路复用技术只是简单且模糊的介绍了Reactor模型,这里再细说下Reactor的单线程模型、多线程模型和主从多线程模型。为什么要介绍Reactor模型,因为Netty的线程模型是Reactor模型的实现。

1.1 单线程模型

Reactor单线程模型仅使用一个线程来处理所有的事情,包括客户端的连接和到服务器的连接,以及所有连接产生的读写事件,这种线程模型需要使用异步非阻塞I/O,使得每一个操作都不会发生阻塞,Handler为具体的处理事件的处理器,而Acceptor为连接的接收者,作为服务端接收来自客户端的链接请求。
在这里插入图片描述
对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发的应用场景却不合适。

1.2 多线程模型

多线程模型下,接收链接和处理请求作为两部分分离了,而Acceptor使用单独的线程来接收请求,做好准备后就交给事件处理的handler来处理,而handler使用了一个线程池来实现,这个线程池可以使用Executor框架实现的线程池来实现。
在这里插入图片描述

多线程模型只有一个线程来处理客户端的连接请求,那如果这个线程挂了,那整个系统任然会变为不可用,而且,因为仅仅由一个线程来负责客户端的连接请求,如果连接之后要做一些验证之类复杂耗时操作再提交给handler线程来处理的话,就会出现性能问题。

1.3 主从多线程模型

主从多线程模型,不再使用一个线程来处理客户端的请求,而是使用一个线程池。该线程池不仅仅可以用于客户端的连接,还可以实现登陆、握手和安全认证等功能。
在这里插入图片描述

2 梳理清楚几个关系

2.1 NioEventLoopGroup与NioServerSocketChannel的关系

无直接关系,NioEventLoopGroup保存一个channelFactory属性,通过该属性可以创建NioServerSocketChannel,不能通过NioEventLoopGroup获取到已经创建的NioServerSocketChannel实例。

2.2 NioServerSocketChannel与EventLoop的关系

EventLoop接口的一个实现类ThreadPerChannelEventLoop中有一个成员变量ch可以保存一个Channel,没在NioEventLoop类中发现用于保存Channel的属性。NioServerSocketChannel的父类AbstractChannel中有一个成员变量eventLoop可以保存一个EventLoop,通过eventLoop()方法可获得实例。

2.3 NioEventLoopGroup与EventLoop的关系

NioEventLoopGroup的父类MultithreadEventExecutorGroup使用children属性保存一个线程池, 线程池中都是EventLoop实例。MultithreadEventExecutorGroup类中还有一个chooser属性,作用是从EventLoop线程池中选择一个实例。

3 Netty中JDK Executor接口的实现

在这里插入图片描述
我们能够看到Executor分成两部分,左侧为SingleThread,右侧为Multithread。SingleThread的最终实现类为NioEventLoop,Multithread最终实现类为NioEventLoopGroup。NioEventLoopGroup中使用一个数组来保存多个NioEventLoopGroup。

在上图的最上方有一个ThreadPerTaskExecutor类,该类实现JDK底层Executor接口的execute方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}

SingleThreadEventExecutor类中有一个executor属性,用于保存一个ThreadPerTaskExecutor实例。SingleThreadEventExecutor类中也实现了JDK底层Executor接口的execute方法,不过,这里的execute方法实际是调用ThreadPerTaskExecutor实例的execute方法。

右侧在AbstractEventExecutorGroup实现了Executor接口的execute方法,这里是execute方法实际是从线程池中选择一个SingleThreadEventExecutor执行其execute方法。如下:

1
2
3
4
5
// AbstractEventExecutorGroup.execute
@Override
public void execute(Runnable command) {
next().execute(command);
}

所以Netty的Executor最终可以总结为三类:

  1. 左侧SingleThread相关类,处理复杂的逻辑,管理任务
  2. 上方的 ThreadPerTaskExecutor类,用于创建线程,处理任务
  3. 右侧Multithread相关类,管理多个SingleThread

三者之间的关系如下:
在这里插入图片描述

参考

  1. 《Netty实战》第7章
  2. 高性能IO之Reactor模式
  3. 两种高效的事件处理模型:Reactor模式和Proactor模式
  4. Reactor经典模式 -单线程