Java 应用的优雅停机总结
优雅停机概念
对一个 Java 应用,一般来说当
main
方法执行完,或者所有的非守护线程执行完,应用就自动退出了我们也可以使用 Java 里的 API
System.exit(0)
强制应用退出。另外 Linux 的kill pid
命令也能实现同样的效果。在这两种退出模式下,Java 应用会立刻关闭,对象被立刻清空、连接直接丢弃,所以会出现一些奇怪的异常,比如IllegalStateException
之类的我们需要有一个机制,能通知到 JVM 让应用退出前,先不再接收新的消息,但正在处理的任务要正常完成,完成特定资源清理,之后才退出应用,这就是优雅关机(graceful shutdown)的概念
Java Shutdown Hook
Liunx 中的应用关闭
我们关闭应用的时候,如果用
kill pid
,相当于发送了一个SIGTERM (15)-Termination
信号,默认情况下程序会忽略,此时应用会直接退出但我们也可以在代码里捕获这个信号并执行处理关机逻辑,从而实现优雅关机,这是一般实现方案
而使用
kill -9 pid
时,该信号无法被程序捕捉,系统会直接关闭进程,不给任何退出时间。注意,我们只能杀死我们 own 的应用,而 root 可以杀死任何应用
Java Shutdown Hook
Java 提供了一个叫关闭钩子(Shutdown Hook)的机制来实现优雅停机
所谓的 Shutdown Hook,其实就是一个我们自己实现的 Thread,然后这个线程被我们通过
Runtime.getRuntime().addShutdownHook(new MyShutdownHook())
注册到了 JVM 上当 JVM 捕获了关闭信号后,会运行所有注册过的 Shutdown Hook,来执行我们的清理工作,之后再退出程序
下面的是一个简单的 Shutdown Hook,运行后通过
kill pid
关闭后,会发现钩子里的日志被正常打印
Java Shutdown Hook 分类
Java 里的 Shutdown Hook 有两类,JVM 级别的和应用级别的,我们通过
Runtime.getRuntime().addShutdownHook()
注册的都是应用级别的应用级别的 Shutdown Hook 会被并发执行,因此要注意线程安全性
下面是应用 Shutdown Hook 的执行逻辑:
Java Shutdown Hook 如何实现关闭信号捕捉
当我们使用
kill pid
关闭应用时,OS 发出一个关闭信息,这个关闭信号被应用捕捉,从而触发了后面关闭流程。那这个信号是如何被 Java 程序捕捉的哪?Java 应用里的信号捕捉是通过
java.lang.Runtime
实现的,每个应用只会有一个Runtime
实例,和底层 OS 绑定捕捉关闭信息能被捕捉的关闭信号主要有:
kill pid
命令、系统关机、登录 shell 退出、CTRL + C 等Shutdown Hook 注意事项
程序退出时,JVM 会并发执行所有的应用 Shutdown Hook,并且只有所有 Shutdown Hook 都执行完,程序才正常退出
另外,执行 Shutdown Hook 时,应该认为应用内的各种服务、资源都已经处于不可靠状态
因此,编写 Shutdown Hook 时要特别小心,不要有死锁。Shutdown Hook 应该是线程安全的,且不依赖于应用资源,比如,假设你的 Shutdown Hook 依赖另一个服务,这个服务又注册了自己的 Shutdown Hook,已经先行一步清理完自己的资源,这时候你的 Shutdown Hook 就会有问题
另外,Shutdown Hook 应该能很快就完成自己的工作。在关机导致的应用退出场景下,OS 只会给应用一小段时间来做自己的事情,过时就强制关闭
Spring Boot 的优雅关机方案
Spring Boot 2.3 为内嵌的容器增加了 graceful shutdown 功能,配置十分简单
Spring Boot 优雅关机流程
当配置了优雅关机后,如果应用被要求关闭,Spring Boot 的内嵌容器(比如 Tomcat),会开始拒绝外部请求
如果内部没有待处理的请求,应用会直接关闭
如果还有在处理的请求,应用会等待请求处理完成,默认是 30 秒,如果 30 秒后还没处理完,应用仍然会关闭,你可以执行设置该时间
参考资料
版权声明: 本文为 InfoQ 作者【陈德伟】的原创文章。
原文链接:【http://xie.infoq.cn/article/9d6abf22069e917b6abd1ac18】。文章转载请联系作者。
评论