NioEventLoopGroup相关类是netty进行TCP网络通信的线程模型,通过其UML类图关系可以看出其本质上是一个线程池对象(继承ScheduledExecutorService接口除了具备线程池特性外,其可以执行定时或者延时任务),可以通过设置相关线程数采用不同形式的Reactor线程模型。
```
/**
* Reactor单线程模型
*/
public void aloneReactor() {
//创建一个线程数为1的NioEventLoopGroup对象 该对象本质上是线程池对象
//从而模拟出Reactor单线程模型
NioEventLoopGroup aloneEventGroup = new NioEventLoopGroup(1);
//NIO服务启动辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
//添加线程池组
bootstrap.group(aloneEventGroup);
//省略部分代码
}
该线程模型只适用于小容量 低并发场景,如果在请求量变大 则一个线程要处理成百上千链路 cpu负荷过重从而导致处理性能变慢或者阻塞(导致某些读写、连接操作超时),而且单线程可靠性无法保证。
/**
* Reactor多线程模型
*/
public void multiReactor() {
//创建一个线程数为1的NioEventLoopGroup对象 用于单独处理客户端连接操作
NioEventLoopGroup acceptEventGroup = new NioEventLoopGroup(1);
//一旦建立连接 IO读写操作交给该线程组去进行处理
NioEventLoopGroup nioReadAndWriteEventGroup = new NioEventLoopGroup();
//NIO服务启动辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
//添加线程池组
bootstrap.group(acceptEventGroup,nioReadAndWriteEventGroup);
//省略部分代码
}
针对Reactor单线程模型的性能瓶颈,Reactor多线程模型使用一个线程单独处理客户端连接
连接建立后,读写请求交个一个线程组去处理,这种线程模型在绝大数应用场景下都能满足性能要求,但是如果并发达到百万级别,且连接请求有复杂的安全认证等功能则会出现性能问题。
所以此处衍生出主从 Reactor多线程模型.
/**
* 主从Reactor多线程模型
*/
public void masterSlaveMultiReactor() {
//创建一个NioEventLoopGroup 线程对象 用于百万并发下处理客户端连接操作
NioEventLoopGroup acceptEventGroup = new NioEventLoopGroup();
//一旦建立连接 IO读写操作交给该线程组去进行处理
NioEventLoopGroup nioReadAndWriteEventGroup = new NioEventLoopGroup();
//NIO服务启动辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
//添加线程池组
bootstrap.group(acceptEventGroup,nioReadAndWriteEventGroup);
//省略部分代码
}
该线程模型提供两个线程组 一个线程组用来处理客户端连接认证等复杂处理(避免了之前单线程处理连接的问题)读写IO操作交由另外一个线程组进行处理。该模型解决了绝大多数业务场景的性能问题。
因为netty的线程模型封装的比较好,好多类并没有实际功能只是提供构造方法,这里我们从代码使用过程去分析其源码,其他边边角角。
NioEventLoopGroup 从功能上,可以把它当做一个线程池来理解,它管理一个包含NioEventLoop的childern列表。
NioEventLoop 从功能上,可以把它当做一个线程来理解,当它启动以后,它就会不停地循环处理三种任务(从类名上也能体现出循环处理的思想:Loop)。这三种任务分别是哪三种任务呢?
下面我们从NioEventLoop创建和使用两个角度对源码进行分析。
NioEventLoop的创建是通过先创建一个NioEventLoopGroup,在由其构建NioEventLoop对象,根据其UML类图其主要核心属性功能在父SingleThreadEventExecutor类中
public NioEventLoopGroup(int nThreads, Executor executor) {
//第三个参数为对应的NIO的多路复用器 该多路复用器实现是基于操作系统的
//不同的操作系统 不同的多路复用器实现 逻辑比较简单:
//先通过loadProviderFromProperty方法 查找 java.nio.channels.spi.SelectorProvider属性对应的SelectorProvider class 并反射实例化
//没有的话 调用loadProviderAsService方法 通过SPI的机制获取服务实例
//还没有默认创建一个WindowsSelectorProvider实现
this(nThreads, executor, SelectorProvider.provider());
}
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
//线程个数如果是0 则默认创建当前服务器CPU核数的两倍。
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
/**
* 1、创建线程执行器:ThreadPerTaskExecutor
* newDefaultThreadFactory()会创建一个线程工厂,该线程工厂的作用就是用来创建线程,
* 同时给线程设置名称:nioEventLoop-1-XX
*/
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//根据设置的线程数创建对应数量的NioEventLoop
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
//创建异常标识
boolean success = false;
try {
//2、创建NioEventLoop对象保存到children数组中
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
//异常处理
//如果在创建某个NioEventLoop的时候异常 则之前创建成功的对象关闭
}
}
//为所有创建成功的NioEventLoop 添加执行中断的监听
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
//同时创建只读视图集合
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
上述方法有两处核心逻辑
rebuild()方法根据传入的Executor和SelectorProvider去创建NioEventLoop对象
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider) {
//调用父类构造器SingleThreadEventExecutor 进行相应属性赋值
//第三个参数 为addTaskWakesUp(为false 则该线程添加线程任务需要唤醒线程 添加一个空的Runnable任务)
//该方法还创建类一个LinkedBlockingQueue
super(parent, executor, false);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
//NioEventLoop需要处理网络IO读写 需要持有一个多路复用器对象
//则下面的两行代码就是创建多路复用器
provider = selectorProvider;
selector = openSelector();
}
private Selector openSelector() {
final Selector selector;
try {
//直接获取原生的多路复用器
selector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
//如果不需要对selector的SelectionKey的hashSet结构进行优化 则直接返回
//否则执行下面的SelectionKey的结构优化为数组(因为HashSet极端情况下插入操作为O(n)数组添加操作为O(1)
//该变量可以io.netty.noKeySetOptimization属性进行配置 默认为false
if (DISABLE_KEYSET_OPTIMIZATION) {
return selector;
}
try {
//创建SelectedSelectionKeySet 将Selector的selectedKeys和publicSelectedKeys
//字段使用创建的set填充
SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
//省略部分部分代码
} catch (Throwable t) {
}
return selector;
}
直接通过SelectorProvider的openSelector获取原生的Selector,通过变量DISABLE_KEYSET_OPTIMIZATION判断是否要对Selector的
SelectionKey的存储结构进行优化(该变量可以io.netty.noKeySetOptimization属性进行配置 默认为false)不需要优化直接返回,否则
使用数据类型的存储结构以反射的形式将Selector实现类的selectedKeys和publicSelectedKeys替换成SelectedSelectionKeySet。
在分析NioEventLoop创建过程中我们了解到最终DefaultThreadFactory的newThread()方法创建线程对象并包装到NioEventLoop中, 从服务启动的堆栈信息流程如下。
newThread:105, DefaultThreadFactory (io.netty.util.concurrent)
execute:33, ThreadPerTaskExecutor (io.netty.util.concurrent)
doStartThread:783, SingleThreadEventExecutor (io.netty.util.concurrent)
startThread:776, SingleThreadEventExecutor (io.netty.util.concurrent)
execute:654, SingleThreadEventExecutor (io.netty.util.concurrent)
register:400, AbstractChannel$AbstractUnsafe (io.netty.channel)
initAndRegister:276, AbstractBootstrap (io.netty.bootstrap)
doBind:234, AbstractBootstrap (io.netty.bootstrap)
bind:230, AbstractBootstrap (io.netty.bootstrap)
bind:205, AbstractBootstrap (io.netty.bootstrap)
start:39, NettyServer (com.xiu.netty.stickyAndUnPack)
main:74, NettyServer (com.xiu.netty.stickyAndUnPack)
在ServerBootstrap的bind()方法中调用io.netty.bootstrap.AbstractBootstrap#doBind()方法,调用io.netty.channel.AbstractChannel.AbstractUnsafe#register()方法
@Override
public final void register(final ChannelPromise promise) {
// 此时Channel实例化完成 将 eventLoop 和这个 channel绑定了,从此这个 channel 就是有 eventLoop 的了
// 我觉得这一步其实挺关键的,因为后续该 channel 中的所有异步操作,都要提交给这个 eventLoop 来执行
//如果发起 register 动作的线程就是 eventLoop 实例中的线程,那么直接调用 register0(promise)
// 对于我们来说,它不会进入到这个分支,
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
//调用EventLoop对象执行 一个Runnable任务(如果没有实例化线程 需要实例化)
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
//省略异常处理
}
}
}
NioEventLoop的启动实际上就是其中的线程对象的实例化,该实例化阶段发生在Channel实例化完成之后,绑定端口之前,这时候需要使用线程去调用Runnable任务(第一个任务是register)如果没有实例化线程对象则就去进行实例化操作。
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
//判断执行代码线程与该EventLoop是否为同一个线程 如果是进行处理
//这里是netty的串行无锁设计模式
//Netty线程间不需要做同步控制,Netty可以通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这样性能就提升了。
boolean inEventLoop = inEventLoop();
//线程已经实例化完成 则直接将任务放入taskQueue(之前NioEventLoop创建过程中会新建该队列)等待调度执行
if (inEventLoop) {
addTask(task);
} else {
//没有实例化先实例化线程 最终调用Executor的execute(ThreadPerTaskExecutor的execute方法)
startThread();
//线程实例化完成 则将任务放入待执行队列
addTask(task);
//线程关闭 则执行任务拒绝
if (isShutdown() && removeTask(task)) {
reject();
}
}
//判断线程阻塞 是否需要唤醒
//当线程执行IO操作的时候的时候, 如果超时时间timeoutMillis还没有到达的情况下,
//IO线程就会处于阻塞状态,此时的IO线程处于阻塞状态,故需要把它唤醒 让IO线程可以及时的处理刚刚非IO线程提交的任务。
if (!addTaskWakesUp) {
wakeup(inEventLoop);
}
}
上述方法主要是针对NioEventLoop中的Thread是否实例化展开,如果实例化完成且代码执行的线程和EventLoop对应的实例化线程一致(netty串行无锁化提升性能),将任务放入线程调度中的待执行队列中。否则需要调用startThread()方法先实例化线程然后将任务放入待执行队列中(涉及到线程关闭的任务拒绝处理),最后判断IO线程阻塞,非IO处理是否需要唤醒线程。
startThread()方法 最终是调用ThreadPerTaskExecutor的execute()实例化线程(该类是NioEventLoop的Executor的线程执行器)调用其中的DefaultThreadFactory类的newThread()方法 创建Thread实例 (代码比较简单 不贴出来)
至此有关NioEventLoop的相关分析到此为止,有不当之处,需要读者指出。