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 ...
随机推荐
- 利用代码生成工具Database2Sharp生成ABP VNext框架项目代码
我们在做某件事情的时候,一般需要详细了解它的特点,以及内在的逻辑关系,一旦我们详细了解了整个事物后,就可以通过一些辅助手段来提高我们的做事情的效率了.本篇随笔介绍ABP VNext框架各分层项目的规则 ...
- elasticsearch之集成中文分词器
IK是基于字典的一款轻量级的中文分词工具包,可以通过elasticsearch的插件机制集成: 一.集成步骤 1.在elasticsearch的安装目录下的plugin下新建ik目录: 2.在gith ...
- HUAWEI网络设备恢复Console口密码
密码遗忘 本章介绍了Console口丢失的处理方法,建议用户妥善保管密码,并定期修改. 恢复Console口密码 设备提供如下方法恢复Console口密码.•方法一:通过STelnet/Telnet登 ...
- 【计算机理论】CSAPP ch2
信息存储 十六进制表示法 (略) 字数据大小 大多数计算机使用8bit的块(字节)作为最小的可寻址的内存单元 字长指明了指针数据的标称大小(?) 64位系统和32位系统向后兼容 C语言中有些数据类型的 ...
- 观察者模式(Observer模式)
模式的定义与特点 观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.这种模式有时又称作发布-订阅模式.模型- ...
- gorm中动态使用表名
用户表(前台.后台) type User struct { ID int `gorm:"primaryKey;autoIncrement"` Name sql.NullString ...
- golang操作mysql
1. 安装mysql驱动库和sqlx基于官方sql库的扩展库 go get github.com/go-sql-driver/mysql go get github.com/jmoiron/sqlx ...
- gorm创建记录及设置字段默认值
package main import ( "database/sql" "gorm.io/driver/mysql" "gorm.io/gorm&q ...
- linux下查看开放的端口
Nmap是一款针对大型网络的端口扫描工具,它也适用于单机扫描,它支持很多扫描,也同时支持性能和可靠性统计. [root@localhost ~]# yum install namp [root@loc ...
- 负载均衡后端状态(proxy_next_upstream 后端错误标识)
目录 一:负载均衡后端状态 二:down(无论什么情况不会分配流量) 三:backup(备用只有当所有的机器宕机(关闭)才能启动备份服务器) 四:max_fails.fail_timeout(结合使用 ...