TOC
Netty服务的关闭涉及以下几种资源:
- 线程
EventLoopGroup
线程池EventLoop
线程
- 连接
EventLoop
管理下的所有Channel
- Selector
- 内存
- Channel下的各种缓存资源
线程的关闭
EventLoopGroup
关闭是从EventLoopGroup调用shutdown相关方法开始的,优雅关闭的方法为:
# EventExecutorGroup.java EventLoopGroup 实现了 此接口
Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);
之所以称之为优雅,是此方要求关闭前必须满足在静默时间quietPeriod
(单位:秒)内无新的task提交到EventLoopGroup
。为了防止无限期等待,还设置了超时时间timeout
。若不提供这两个值,则使用默认值quietPeriod=2, timeout=15, unit=TimeUnit.Second
。
下面开始上代码,首先是NioEventLoopGroup
在父类MultithreadEventExecutorGroup
实现了shutdownGracefully()
方法:
# MultithreadEventExecutorGroup.java
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
for (EventExecutor l: children) {
l.shutdownGracefully(quietPeriod, timeout, unit);
}
return terminationFuture();
}
作为线程池,本身不维护什么信息,任务都是分给具体的线程EventExecutor
去执行,这里的EventExecutor
(接口)实际就是NioEventLoop
(类)实例,后者实现前者。
这里不贴NioEventLoop
的shutdownGracefully()
的方法,简单概括就是将NioEventLoop
的状态变量修改为ST_SHUTTING_DOWN
,等待线程方法自己判断状态变化后进行关闭。
EventLoop
# NioEventLoop.java
protected void run() {
for (;;) {
// 业务逻辑,包括处理SelectionKey事件
try {...} catch (Throwable t) {...}
try {
// 判断正在结束此workerThread, 跳出循环
if (isShuttingDown()) {
// 关闭
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
private void closeAll() {
// 触发一次selector#selectNow方法,获取已接收但未处理的事件
selectAgain();
Set<SelectionKey> keys = selector.keys();
Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
// 当前是NioEventLoop, 仅处理NioChannel的关闭
for (SelectionKey k: keys) {
Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
channels.add((AbstractNioChannel) a);
} else {
k.cancel();
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
invokeChannelUnregistered(task, k, null);
}
}
for (AbstractNioChannel ch: channels) {
// 实际执行channel关闭
ch.unsafe().close(ch.unsafe().voidPromise());
}
}
在NioEventLoop
的run()方法内,每次循环都会判断状态,若为关闭,
- 先执行
closeAll()
方法,获取Selector注册的所有SelectionKey,从后者中提取出Channel,执行关闭。关闭方法跟断开连接所用的方法一致,不再赘述。但在线程中可能还有别的任务在执行,所以需要优雅关闭。 confirmShutdown()
判断是否完成关闭,内部是优雅关闭的逻辑。超时或者完成关闭,返回true,NioEventLoop关闭完成,退出循环。否则会再次进入run方法内的循环。
优雅关闭
protected boolean confirmShutdown() {
// (省略) 状态判断
// 取消所有定时任务的执行
cancelScheduledTasks();
if (gracefulShutdownStartTime == 0) {
// 记录当前相对时间戳, 整个netty进程启动到今的相对时间
gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();
}
// 尝试执行队列中的task和shutdownHooks,
// 如果执行了新的任务会更新this#lastExecutionTime的值,并返回true
if (runAllTasks() || runShutdownHooks()) {
if (isShutdown()) {
// Executor shut down - no new tasks anymore.
return true;
}
if (gracefulShutdownQuietPeriod == 0) {
return true;
}
wakeup(true);
return false;
}
final long nanoTime = ScheduledFutureTask.nanoTime();
// 已经关闭 或者 超时
if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
return true;
}
// 在静默时间内有任务被执行,故返回false,本轮优雅关闭失败
if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
wakeup(true);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Ignore
}
return false;
}
// 现在可以判断在静默时间段内,无新的任务执行过,返回true,现在可以放心关闭服务了
return true;
}
总结
本章给你介绍了Netty关闭服务的一些内容,由于Netty工作在React线程模型中,所以关闭服务也就是把worker
和boss
线程池关闭。
每个线程池的关闭,都落地为线程本身,也即NioEventLoop
在判断线程状态为ST_SHUTTING_DOWN
之后主动地优雅关闭。
优雅关闭的第一步是关闭连接,其次是反复地尝试优雅关闭
直到超时。