常见Java应用如何优雅关闭

发表于:2017-12-19 09:55

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:卢利如    来源:点我达技术

  一、前言
  在我们进行系统升级的时候,往往需要关闭我们的应用,然后重启。在关闭应用前,我们希望做一些前置操作,比如关闭数据库、redis连接,清理zookeeper的临时节点,释放分布式锁,持久化缓存数据等等。
  二、Linux的信号机制
  在linux上,我们关闭进程主要是使用kill <pid>的方式。
  当执行该命令以后,linux会向进程发送一个信号,进程收到以后之后,可以做一些清理工作。
  kill命令默认的信号值为15,即SIGTERM信号。
  通过kill -l查看linux支持哪些信号:
  linux提供了signal()api,可以将信号处理函数注册上去:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
static void gracefulClose(int sig)
{
printf("执行清理工作\n");
printf("JVM 已关闭\n");
exit(0);    //正常关闭
}
int main(int argc,char *argv[])
{
if(signal(SIGTERM,gracefulClose) == SIG_ERR)
exit(-1);
printf("JVM 已启动\n");
while(true)
{
// 执行工作
sleep(1);
}
}
  三、Java提供的Shutdown Hook
  Java并不支持类似于linux的信号机制,但是提供了Runtime.addShutdownHook(Thread hook)的api。
  在JVM关闭前,会并发执行各个Hook线程。
public class ShutdownHook {
public static void main(String[] args) throws InterruptedException {
Runtime.getRuntime().addShutdownHook(new DbShutdownWork());
System.out.println("JVM 已启动");
while(true){
Thread.sleep(10L);
}
}
static class DbShutdownWork extends Thread{
public void run(){
System.out.println("关闭数据库连接");
}
}
}
  四、Spring Boot提供的优雅关闭功能
  我们一般采用如下的方式,启动一个Spring boot应用:
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleController.class, args);
}
  SpringApplication.run()代码如下,会调用到refreshContext(context)方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
  refreshContext()方法比较简单:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);   //调用ApplicationContext.refresh()
if (this.registerShutdownHook) {        //registerShutdownHook默认值为true
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
AbstractApplicationContext.registerShutdownHook()代码:
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread() {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
  很明显,Spring boot通过在启动时,向JVM注册一个ShutdownHook,从而实现JVM关闭前,正常关闭Spring容器。而Spring在销毁时,会依次调用bean的destroy动作来销毁。
  五、Dubbo的优雅关闭策略
  Dubbo同样是基于ShutdownHook实现的。
  AbstractConfig的static代码:
static {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Run shutdown hook now.");
}
ProtocolConfig.destroyAll();
}
}, "DubboShutdownHook"));
}
  六、总结
  只要我们的应用运行在linux平台上,所有的优雅关闭方案都是基于linux提供的信号机制提供的,JVM也是如此。
  Java并没有为我们提供与之一一对应的api,而是给出了个ShutdownHook机制,也能达到类似的效果,缺点是我们无法得知JVM关闭的原因。
  像dubbo、spring boot等成熟的开源框架,都实现了自动注册ShutdownHook的功能,从而避免使用者忘记调用优雅关闭api引发问题,降低框架的使用难度。

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号