使用钩子shutdownHook优雅关闭资源
1.前言
这是一个笔者在造rpc轮子的时候诞生的需求,也是一个挺细节的小知识点。故写一篇blog用于记录。
这里先来简单讲一下rpc。rpc,远程方法调用,他旨在屏蔽了很多细节让我们调用远程方法和调用本地方法一样简单,目前存在的rpc框架就有dubbo、gRpc等
想想平时对接第三方api时是不是经历http请求、序列化、拆箱校验、数据标准化装箱等操作呢
而rpc在使用时也涉及道诸多系统资源的使用,例如线程池、注册中心(zookeeper等)等,当rpc服务端(服务提供方)停止服务时,是否也应该释放这些资源呢?
结果是必然的。想到这一层,需求就诞生了,经过多方寻找,类似于git + jenkins上的webHook,也就是shutdownHook钩子浮出水面
2.shutdownHook
这是JVM本身提供的功能,当 JVM 接受到系统的关闭通知之后,调用 ShutdownHook 内的方法,用以完成清理操作,从而平滑的退出应用,下面是示例代码:
CustomShutdownHook.class
@Slf4j
public class CustomShutdownHook {
private static final CustomShutdownHook CUSTOM_SHUTDOWN_HOOK = new CustomShutdownHook();
public static CustomShutdownHook getCustomShutdownHook() {
return CUSTOM_SHUTDOWN_HOOK;
}
public void releaseResources() {
log.info("addShutdownHook for clearAll");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// 1.删除服务端注册信息
ZookeeperClient.clearRegistry();
// 2.关闭所有线程池
ThreadPoolExecutorUtils.shutDownAllThreadPool();
}));
}
}
自定义的shutdownHook,用于关闭服务端在zookeeper上注册的信息以及线程池资源
RpcServer.class
public class RpcServer {
public void startup(){
// 校验
...
// 添加JVM钩子 用于应用关闭时 自动释放资源
CustomShutdownHook.getCustomShutdownHook().releaseResources();
// 启动服务端
...
}
}
服务端启动时add自定义的ShutDownHook
关闭应用
此方式支持在以下几种场景优雅停机:
1.程序正常退出
2.使用System.exit()
3.终端使用Ctrl+C
4.使用Kill pid干掉进程
3.Tips
1. 不要使用kill -9来结束你的应用程序
kill -9是操作系统的终极杀器,如果你使用kill -9的话,应用程序是没有任何发言权的,它只能选择默默退出,几乎可以肯定,shutdownHook不会被调用。
2. shutdownHook涉及的方法应该尽量的短
这个可能和操作系统有关系,不同的操作系统可能有不同的差异:
When a computer shuts down, the final stage of the shutdown process sends every remaining process a SIGTERM, gives those processes a few seconds grace, then sends them a SIGKILL.
也就是说,如果shutdownHook过长,可能方法还没执行完,进程就被操作系统强制杀掉了,这一点在addShutdownHook()的文档上也有提及:
* <p> Shutdown hooks should also finish their work quickly. When a
* program invokes {@link #exit exit} the expectation is
* that the virtual machine will promptly shut down and exit. When the
* virtual machine is terminated due to user logoff or system shutdown the
* underlying operating system may only allow a fixed amount of time in
* which to shut down and exit. It is therefore inadvisable to attempt any
* user interaction or to perform a long-running computation in a shutdown
* hook.
3. shutdownHook的方法应该是线程安全的
这是因为,用户可能多次发送信号导致方法被不同的线程被多次调用,关于这一点文档也有说明:
* They should, in
* particular, be written to be thread-safe and to avoid deadlocks insofar
* as possible.
4. 关于shutdownHook方法的异常
shutdownHook调用过程中产生的所有异常都会被忽略掉并且可能不会输出任何提示信息,因此程序可能蕴含了一个久久不能被发现的BUG导致你的shutdownHook无法被执行,在调用shutdownHook的过程中,一定要仔细检查你的代码,保证正确性。
5. 某些场景下要提供at most once的保证
这点其实是接第三点说的,就是你的shutdownHook可能被调用多次,但其实关闭一次就够了,多次调用可能会引发一些意想不到的异常。比如KafkaStream的close方法,就提供了这样的保证:
public synchronized boolean close(long timeout, TimeUnit timeUnit) {
this.log.debug("Stopping Streams client with timeoutMillis = {} ms.", timeUnit.toMillis(timeout));
if (!this.setState(KafkaStreams.State.PENDING_SHUTDOWN)) {
this.log.info("Already in the pending shutdown state, wait to complete shutdown");
} else {
// ....
可以使用CAS操作来做这样的检查:
if (state.compareAndSet(ACTIVE, CLOSED)) {
// close here
}
【参考链接】:
1:微服务项目优雅上线、下线小技巧,你学废了吗
2:Java中使用shutdownHook的一些注意事项
3:ShutdownHook- Java 优雅停机解决方案
4:SpringBoot 2.3.0正式发布:优雅停机,配置文件位置新特性一览
5:Spring Boot 系列:最新版优雅停机详解
6:回调方法?钩子方法?模板模式?
7:拒绝 kill -9, SpringBoot 优雅停机详解!
8:Java程序优雅关闭的两种方法