文章目录
Netty 核心原理之运行机制
一、包含的知识点
- Reactor线程模型
- EventLoopGroup和Reactor之间的关联
- EventLoop和Channel的关联
- EventLoop启动流程
二、Reactor线程模型
在文章 Netty基本原理 中已经大致介绍过Reactor线程模型, 为了和后面内容承上启下, 这里再做一个简单介绍。Reactor主要分为
- Reactor单线程模型
- Reactor多线程模型
- Reactor主从多线程模型
2.1 Reactor单线程模型
单线程模型是指Acceptor和Handler在同一个线程中处理; 这种线程模型存在下面问题
- 如果某个Handler阻塞, 会导致其它Client的Handler被阻塞, 不能进行业务处理
- Acceptor也会被阻塞, 不能接受新的Client请求
//单线程模型使用示例代码
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup);
2.2 Reactor多线程模型
多线程模型和单线程模型区别是, 各个Client连接的IO操作由一组NIO线程提供, Acceptor还是由一个线程来处理, 其特点如下:
- Acceptor有一个专门的线程用于处理客户端的TCP连接请求
- 客户端连接的IO操作由一个特定的NIO线程池负责
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(100);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workerGroup);
Note: 一个Client的IO操作只能由同一个线程执行, 但是一个线程可以处理多个Client的IO操作
2.3 Reactor主从多线程模型
Reactor多线程模型的acceptor只有一个线程处理客户端的TCP请求, 如果高并发情况下有大量客户端发起连接请求, 可能造成大量客户端无法连接到服务端。为了解决这个问题, Reactor主从多线程模型, 对接收客户端的连接请求不在是一个线程, 而是一个独立的线程池,即acceptor使用了线程池来处理客户端的请求。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup,workerGroup);
三、EventLoopGroup实例化流程
3.1 EventLoopGroup类继承关系
首先我们看下EventLoopGroup的类继承关系, 然后基于下面的代码分析EventLoopGroup初始化过程。
EventLoopGroup bossGroup = new NioEventLoopGroup();
3.2 EventLoopGroup创建流程
创建EventLoopGroup时, 通过重载方式创建, 看下下面的代码
//NioEventLoopGroup
public NioEventLoopGroup() {
this(0);
}
//... 省略部分其它构造函数
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
//调用的是父类 MultithreadEventLoopGroup 创建NioEventLoopGroup对象
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
//MultithreadEventLoopGroup
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
// 继续调用父类 MultithreadEventExecutorGroup 进行重建
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
//MultithreadEventExecutorGroup
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
// 创建NioEventLoopGroup的实际入口
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
从代码实现和类继承关系, 创建NioEventLoopGroup的入口是MultithreadEventExecutorGroup,下面是具体实现逻辑代码
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
//省略参数校验逻辑
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
/**
* 调用抽象方法进行 children 数组创建, 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 {
//省略服务停止逻辑
}
}
chooser = chooserFactory.newChooser(children);
//创建服务终止监听器Listener
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
// 给每个children添加刚刚创建的监听器
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
//创建children的只读副本 childrenSet
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
3.3 总结
-
EventLoopGroup的创建逻辑是在MultithreadEventExecutorGroup实现的, 内部维护了一个EventExecutor childrean数组
-
EventExecutor数组的长度由nThreads指定, 如果给定初始值则由给定值决定, 如果没有给定初始值, 则是CPU核数 * 2
-
EventExecutor数组是由newChild抽象方法创建, 这个方法的逻辑在NioEventLoopGroup类中实现, 返回NioEventLoop对象
-
初始化NioEventLoop主要属性
4.1 通过args[0] 获得SelectorPrivider对象, 再通过provider对象的openSelector()方法获得Selector对象
4.2 通过args[1] 获得SelectorStrategyFactory对象, 再通过newSelectStrategy获得对应的具体策略
4.3 通过args[2] 获得拒绝策略Handler
四、EventLoop任务执行者
4.1 EventLoop类继承关系
在第三节中我们分析知道, MultithreadEventExecutorGroup内部维护了EventExcutor children数组, 数组元素通过newChild创建, 这个方法是一个抽象方法, 是在NioEventLoopGroup实现, 返回NioEventLoop对象, 下面是类即成关系图
从类继承关系可以看出, NioEventLoopGroup创建的NioEventLoop对象是EventExecutor的子类, 而NioEventLoop继承自SingleThreadEventExecutor类, SingleThreadEventExecutor是Netty的本地线程对象
4.2 SingleThreadEventExecutor
SingleThreadEventExecutor是Netty的本地线程对象, 内部维护了一个volatile修饰的Thread对象, 下面是关键的部分代码, thread存储了一个本地java线程, 当创建NioEventLoop对象时, 实际是和一个特定的线程进行绑定, 在生命周期内, 绑定的线程不会改变(从Thread用volatile修饰也可以知道 thread基本是只读的)
private final Queue<Runnable> taskQueue; // 任务队列taskQueue
private volatile Thread thread; // 执行业务逻辑线程thread
@SuppressWarnings("unused")
private volatile ThreadProperties threadProperties; //线程属性
private final Executor executor; // 线程池对象root
private volatile boolean interrupted; // 中断标志
private final Semaphore threadLock = new Semaphore(0);
private final Set<Runnable> shutdownHooks = new LinkedHashSet<Runnable>(); // 钩子, 停止线程
private final boolean addTaskWakesUp;
private final int maxPendingTasks; // 最大pending任务数量
private final RejectedExecutionHandler rejectedExecutionHandler; // 线程拒绝策略handler
private long lastExecutionTime; // 上次执行时间
@SuppressWarnings({ "FieldMayBeFinal", "unused" })
private volatile int state = ST_NOT_STARTED;
private volatile long gracefulShutdownQuietPeriod; //平滑停止服务优先级
private volatile long gracefulShutdownTimeout; //平滑停止服务超时时间
private long gracefulShutdownStartTime; // 平滑停止服务开始时间
4.3 NioEventLoop作用
NioEventLoop有两个作用
- 第一个任务是作为IO线程, 执行Channel相关的IO操作, 实际调用Selector上等待就绪的事件
- 执行taskQueue队列中的任务, 或eventLoop.schedule提交的任务队列
从之前讲解Channel和Selector知识, 对上面第一点比较容易理解, 对第二点提交的定时任务处理怎么理解呢?
- NioEventLoop实例的execute()方法向任务队列taskQueue添加任务, 并进行调度执行
- NioEventLoop的类继承关系, NioEventLoop -> SingleThreadEventLoop -> SingleThreadEventExecutor -> AbstractScheduledEventExecutor, 在AbstractScheduledEventExecutor抽象类中有个任务队列变量, 如下,在SingleThreadEventLoop类又实现了任务队列功能
Queue<ScheduledFutureTask<?>> scheduledTaskQueue;
4.4 线程启动流程
NioEventLoop启动时会执行run方法, 如果当前选择策略是SelectStrategy.SELECT, 会执行select方法
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT: // 如果KEY是 SELECT, 执行select操作
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
// fallthrough
}
// ... 省略其它代码
}
在进行select操作时,如果需要rebuild操作,会执行rebuildSelector()方法
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
// ... 省略部分其它代码
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// timeoutMillis elapsed without anything selected.
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { // 自动rebuild阈值>0, 且selectCnt>阈值, 进行rebuild操作
// The selector returned prematurely many times in a row.
// Rebuild the selector to work around the problem.
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
rebuildSelector();
selector = this.selector;
// Select again to populate selectedKeys.
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
// ...省略部分其它代码
}
因为启动时主线程和当前调用线程是不一样的,初始时刻 inEventLoop()方法为false 查看下面代码
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
因为inEventLoop为false, 会执行execute方法,
public void rebuildSelector() {
if (!inEventLoop()) {
execute(new Runnable() {
@Override
public void run() {
rebuildSelector();
}
});
return;
}
// ... 省略其它代码
}
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread(); // 线程启动
addTask(task); // 将任务添加到taskQueue队列中
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
因为inEventLoop为false, 会执行else对应的逻辑,执行startThread方法进行线程启动, 实际执行的是doStartThread方法, 启动线程逻辑是SingleThreadEventExecutor.this.run(), 实现类在NioEventLoop, 和本小节开始时分析相对应。
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run(); // 启动线程, 抽象方法,NioEventLoop是对应的实现,
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// ... 省略部分代码
}
});
}
五、EventLoop和Channel关系
5.1 ServerBootStrap服务启动
ServerBootStrap服务在启动后会进行服务启动和register操作, 查看下面的代码示例
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup,workerGroup);
server.bind(port) ;
服务通过bind进行端口绑定和服务启动操作, 内部实际调用的是doBind()、bind0()操作
//AbstractBootstrap
public ChannelFuture bind(SocketAddress localAddress) {
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
//
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister(); // 核心: 将Channel和EventLoop进行绑定操作
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// ... 省略其它bind0() 操作
}
//初始话Channel并和NioEventLoop绑定
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel(); // ChannelFactory创建Channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
ChannelFuture regFuture = config().group().register(channel); // channel和NioEventLoop绑定核心逻辑
return regFuture;
}
5.2 EventLoopGroup创建
config()是一个抽象类, 分别根据ServerBoostrap、Bootstrap有不同的实现, 这里以ServerBootStrap为例进行说明, 方法执行后会创建ServerBootstrapConfig
public final ServerBootstrapConfig config() {
return config; // 返回 ServerBootstrapConfig
}
ServerBootstrapConfig继承自AbstractBootstrapConfig, 在AbstractBootstrapConfig类中, 通过下面的代码创建EventLoopGroup对象
//ServerBootstrap
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this); // this就是ServerBootStrap
//ServerBootstrapConfig
public final EventLoopGroup group() {
return bootstrap.group();
}
//AbstractBootStrap <- ServerBootstrap
public final EventLoopGroup group() {
return group; // group是bossGroup(EventLoopGroup)
}
EventLoopGroup是一个抽象类, 其核心子类是MultithreadEventLoopGroup, 内部通过register方法将Channel和EventLoopGroup进行绑定,查看接口调用
//MultithreadEventLoopGroup -> EventLoopGroup
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
//SingleThreadEventLoop
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
// 通过unsafe方法进行注册, 将EventLoop和Channel绑定
// 这里unsafe是Netty自己定义的接口, 不是操作系统native操作Unsafe
promise.channel().unsafe().register(this, promise);
return promise;
}
5.3 EventLoop启动流程补充
在4.4节中, 我们初步分析了EventLoop的启动流程, 这里再做一个补充。 从4.2节我们知道NioEventLoop的父类SingleThreadEventExecutor内部维护了一个Thread, 因此NioEventLoop的启动实际是对这个thread的启动,我们知道thread的启动是通过thread.start()来启动的,现在从这个思路在SingleThreadEventExecutor类中进行查询, 发现下面的代码
private void startThread() {
if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
doStartThread();
}
}
}
STATE_UPDATER是一个原子类集合, 内部维护了线程状态, 初始时刻线程状态是ST_NOT_STARTED, 会执行if条件内部的逻辑, 在CAS成功后会执行doStartThread()方法, 进行线程启动
private static final AtomicIntegerFieldUpdater<SingleThreadEventExecutor> STATE_UPDATER;
startThread方法是私有方法, 它是在execute方法调用的,下面是源码, 因为服务启动是在主线程中运行的, 下面代码走else逻辑, 执行startThread操作, 并将task加入taskQueue队列中。
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
那么execute()什么时候会被调用呢 ?
在4.4节中我们分析知道,如果需要rebuildSelector, 会执行execute方法; 除此之外,Channel和EventLoop进行register绑定的时候, 也会执行execute方法, 查看下面代码, 服务启动是在主线程中执行的, 进行NIO操作的Thread和主线程不同, 会执行else逻辑, 最后执行EventLoop的execute方法
//AbstractChannel
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 省略部分其它代码
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) { // 主线程启动和当前线程不同, 返回false
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
总结: 当EventLoop执行execute方法时, 会触发startThread方法调用, 进而对Java本地线程启动。