Netty源码解析一——线程池模型之线程池NioEventLoopGroup
本文基础是需要有Netty的使用经验,如果没有编码经验,可以参考官网给的例子:https://netty.io/wiki/user-guide-for-4.x.html。另外本文也是针对的是Netty 4.1.x版本的。
Reactor模式
本文主要介绍Netty线程模型及其实现,介绍Netty线程模型前,首先会介绍下经典的Reactor线程模型,目前大多数网络框架都是基于Reactor模式进行设计和开发,Reactor模式基于事件驱动,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers。这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler,非常适合处理海量的I/O事件。下面简单介绍下Reactor模式及其线程模型。
单线程模型

如图所示,由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会导致阻塞。通常Reactor线程中聚合了多路复用器负责监听网络事件,当有新连接到来时,触发连接事件,Disdatcher负责使用Acceptor接受客户端连接,建立通信链路。当I/O事件就绪后,Disdatcher负责将事件分发到对应的event handler上负责处理。
该模型的缺点很明显,不适用于高负载、高并发的应用场景;由于只有一个Reactor线程,一旦故障,整个系统通信模块将不可用。
多线程模型

该模型的特点:
- 专门由一个Reactor线程-Acceptor线程用于监听服务端,接收客户端连接请求;
- 网络I/O操作读、写等由Reactor线程池负责处理;
- 一个Reactor线程可同时处理多条链路,但一条链路只能对应一个Reactor线程,这样可避免并发操作问题。
绝大多数场景下,Reactor多线程模型都可以满足性能需求,但是,在极个别特殊场景中,一个Reactor线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。因此,诞生了第三种线程模型。
主从多线程模型

该模型的特点:
- 服务端使用一个独立的主Reactor线程池来处理客户端连接,当服务端收到连接请求时,从主线程池中随机选择一个Reactor线程作为Acceptor线程处理连接;
- 链路建立成功后,将新创建的SocketChannel注册到sub reactor线程池的某个Reactor线程上,由它处理后续的I/O操作。
Netty线程模型
Netty同时支持Reactor单线程模型 、Reactor多线程模型和Reactor主从多线程模型,用户可根据启动参数配置在这三种模型之间切换。Netty线程模型原理图如下:

服务端启动时,通常会创建两个NioEventLoopGroup实例,对应了两个独立的Reactor线程池。常见服务端启动代码实现如下:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
......
实际上比较重要的创建线程池创建代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
服务端启动时创建了两个NioEventLoopGroup,他们实际上时两个独立的Reactor线程池,一个负责接收客户端的TCP连接,另一个用于处理I/O操作,或执行系统Task、定时任务Task等,接下来做一下源码分析。
NioEventLoopGroup
首先看下NioEventLoopGroup的继承关系:

可以看出最终还是调用了java的线程池创建方式,接下来看一下它的构造方法。
查看代码
public class NioEventLoopGroup extends MultithreadEventLoopGroup {
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
public NioEventLoopGroup(ThreadFactory threadFactory) {
this(0, threadFactory, SelectorProvider.provider());
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
this(nThreads, threadFactory, SelectorProvider.provider());
}
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
public NioEventLoopGroup(
int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory,
final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
public NioEventLoopGroup(
int nThreads, Executor executor, final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
RejectedExecutionHandlers.reject());
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler);
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler,
final EventLoopTaskQueueFactory taskQueueFactory) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
rejectedExecutionHandler, taskQueueFactory);
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
SelectorProvider selectorProvider,
SelectStrategyFactory selectStrategyFactory,
RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory taskQueueFactory,
EventLoopTaskQueueFactory tailTaskQueueFactory) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory);
}
}
可以看到最终是调用了父类MultithreadEventExecutorGroup的构造方法,继续跟踪:
查看代码
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
checkPositive(nThreads, "nThreads");
if (executor == null) {
//创建线程执行器以及线程工厂
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//根据线程数构建EventExecutor数组
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//初始化数组中的线程,由NioEventLoopGroup创建NioEventLoop实例
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 {
//当初始化失败时,需要优雅关闭,清理资源
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
//当线程未终止时,等待终止
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
//根据线程数创建选择器,选择器主要适用于next()方法
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
//为每个EventLoop线程增加线程终止监听器
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
//创建执行器数组只读副本,便于在迭代查询时使用
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
从代码看出,使用EventLoopGroup workerGroup = new NioEventLoopGroup()来创建线程池,如果不指定线程个数,那么默认用0,在默认为0的情况下系统会使用默认的线程个数来创建线程池,如果制定了n>0个线程个数的话,就创建有限个数线程的线程池。那么默认创建的线程个数规则是啥呢?可以详见如下代码:
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
可以看出是在CPU核心数*2与“io.netty.eventLoopThreads”这个配置参数取或并且与1比较大小得到的结果,就说如果“io.netty.eventLoopThreads”这系统参数配置了就用系统参数与1比较的最大值返回,如果没有配置使用cpu核心数*2与1比较的最大值返回。
关于NioEventLoopGroup我们从代码跟踪中做如下的总结,该类主要是完成三件事:
- 创建一定数量的NioEventLoop线程组并初始化。
- 创建线程选择器chooser,当获取线程时,通过选择器来获取。
- 创建线程工厂并构建线程执行器。
线程组的生产分两步:第一步,创建一定数量的EventExecutor数组;第二步,通过调用子类的newChild()方法完成这些EventExecutor数组的初始化。为了提高可扩展性,Netty的线程组除了NioEventLoopGroup,还有Netty通过JNI方式提供的一套由epoll模型实现的EpollEventLoop Group线程组,以及其他I/O多路复用模型线程组,因此newChild()方法由具体的线程组子类来实现。
children[i] = newChild(executor, args);
查看代码
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
SelectorProvider selectorProvider = (SelectorProvider) args[0];
SelectStrategyFactory selectStrategyFactory = (SelectStrategyFactory) args[1];
RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) args[2];
EventLoopTaskQueueFactory taskQueueFactory = null;
EventLoopTaskQueueFactory tailTaskQueueFactory = null;
int argsLength = args.length;
if (argsLength > 3) {
taskQueueFactory = (EventLoopTaskQueueFactory) args[3];
}
if (argsLength > 4) {
tailTaskQueueFactory = (EventLoopTaskQueueFactory) args[4];
}
return new NioEventLoop(this, executor, selectorProvider,
selectStrategyFactory.newSelectStrategy(),
rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory);
}
在newChild()方法中,NioEventLoop的初始化参数有6个:第1个参数为NioEventLoopGroup线程组本身;第2个参数为线程执行器,用于启动线程,在SingleThreadEventExecutor的doStartThread()方法中被调用;第3个参数为NIO的Selector选择器的提供者;第4个参数主要在NioEventLoop的run()方法中用于控制选择循环;第5个参数为非I/O任务提交被拒绝时的处理Handler;第6个参数为队列工厂,在NioEventLoop中,队列读是单线程操作,而队列写则可能是多线程操作,使用支持多生产者、单消费者的队列比较合适,默认为MpscChunkedArrayQueue队列。
NioEventLoopGroup通过next()方法获取NioEventLoop线程,最终会调用其父类MultithreadEventExecutorGroup的next()方法,委托父类的选择器EventExecutorChooser。具体使用哪种选择器对象取决于MultithreadEventExecutorGroup的构造方法中使用的策略模式。
根据线程条数是否为2的幂次来选择策略,若是,则选择器为PowerOfTwoEventExecutorChooser,其选择策略使用与运算计算下一个选择的线程组的下标index;若不是,则选择器为GenericEventExecutorChooser,其选择策略为使用求余的方法计算下一个线程在线程组中的下标index。其中,PowerOfTwoEventExecutorChooser选择器的与运算性能会更好。
根据线程条数是否为2的幂次来选择策略,若是,则选择器为PowerOfTwoEventExecutorChooser,其选择策略使用与运算计算下一个选择的线程组的下标index,此计算方法在第7章中也有相似的应用;若不是,则选择器为GenericEventExecutorChooser,其选择策略为使用求余的方法计算下一个线程在线程组中的下标index。其中,PowerOfTwoEventExecutorChooser选择器的与运算性能会更好。
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
由于Netty的NioEventLoop线程被包装成了FastThreadLocalThread线程,同时,NioEventLoop线程的状态由它自身管理,因此每个NioEventLoop线程都需要有一个线程执行器,并且在线程执行前需要通过线程工厂io.netty.util.concurrent.DefaultThreadFactory将其包装成FastThreadLocalThread线程。线程执行器ThreadPerTaskExecutor与DefaultThreadFactory的newThread()方法的代码解读如下:
查看代码
public void execute(Runnable command) {
//调用线程工厂类的newThread包装线程,并且启动,等待线程调度。
threadFactory.newThread(command).start();
}
public Thread newThread(Runnable r) {
//包装FastThreadLocalThread线程,线程的前缀名字为NioEventLoopGroup-
//服务启动后可以通过Arthas工具查看
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
try {
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
}
//包装为FastThreadLocalThread线程
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
Netty源码解析一——线程池模型之线程池NioEventLoopGroup的更多相关文章
- Netty 源码解析(五): Netty 的线程池分析
今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
- Netty 源码解析(三): Netty 的 Future 和 Promise
今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...
- Netty 源码解析(九): connect 过程和 bind 过程分析
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(八): 回到 Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(七): NioEventLoop 工作流程
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(六): Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一 ):开始 Netty ...
- Netty 源码解析(四): Netty 的 ChannelPipeline
今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
- Netty 源码解析(二):Netty 的 Channel
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...
- Netty源码解析—客户端启动
Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...
- Netty源码解析---服务端启动
Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...
随机推荐
- Solon 开发
Solon 开发 一.注入或手动获取配置 二.注入或手动获取Bean 三.构建一个Bean的三种方式 四.Bean 扫描的三种方式 五.切面与环绕拦截 六.提取Bean的函数进行定制开发 七.自定义注 ...
- 《剑指offer》面试题55 - II. 平衡二叉树
问题描述 输入一棵二叉树的根节点,判断该树是不是平衡二叉树.如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树. 示例 1: 给定二叉树 [3,9,20,null,null, ...
- Cesium源码剖析---Ambient Occlusion(环境光遮蔽)
Ambient Occlusion简称AO,中文没有太确定的叫法,一般译作环境光遮蔽.百度百科上对AO的解释是这样的:AO是来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善漏光 ...
- 使用Xamarin开发移动应用示例——数独游戏(一)项目的创建与调试
最近项目中需要移动客户端,由于团队基本上使用.Net产品线,所以决定使用Xmarin进行开发,这样技术路线统一,便于后期维护.官网上是这样介绍的" Xamarin 允许你使用 .NET 代码 ...
- 淘系工程师讲解的使用Spring特性优雅书写业务代码
使用Spring特性优雅书写业务代码 大家在日常业务开发工作中相信多多少少遇到过下面这样的几个场景: 当某一个特定事件或动作发生以后,需要执行很多联动动作,如果串行去执行的话太耗时,如果引入消息中 ...
- zabbix表达式
system.cpu.load[all,avg1].min(10)}>2 load连续10分钟>2 system.cpu.load[percpu,avg1].count(#3,0.6,&q ...
- PowerDotNet平台化软件架构设计与实现系列(12):HCRM人员管理平台
技术服务于业务,良好的技术设计和实现能够大幅提升业务质量和效率. PowerDotNet已经形成了自己的开发风格,很多项目已被应用于生产环境,可行性可用性可靠性都得到了生产环境验证. 编程是非常讲究动 ...
- linux关闭防火墙与开启防火墙命令
一:下面是red hat/CentOs7关闭防火墙的命令! 1:查看防火状态 systemctl status firewalld service iptables status 2:暂时关闭防火墙 ...
- linux判断物理CPU,逻辑CPU和CPU核数
① 物理CPU 实际Server中插槽上的CPU个数 物理cpu数量,可以数不重复的 physical id 有几个 ② 逻辑CPU Linux用户对 /proc/cpuinfo 这个文件肯定不陌生. ...
- 从故纸堆里,回顾下Web技术的发展历程
通过对比这些年的计算机图书来让大家感受下前些年Web技术的发展历程. Web开发框架,目前是Spring Boot+JPA,我正好出过本书,从中大家能感受到现在的技术. <Spring Boot ...