常见Java应用如何优雅关闭

一、前言

在我们进行系统升级的时候,往往需要关闭我们的应用,然后重启。在关闭应用前,我们希望做一些前置操作,比如关闭数据库、redis连接,清理zookeeper的临时节点,释放分布式锁,持久化缓存数据等等。

二、Linux的信号机制

在linux上,我们关闭进程主要是使用 kill 的方式。

当执行该命令以后,linux会向进程发送一个信号,进程收到以后之后,可以做一些清理工作。

kill 命令默认的信号值为 15 ,即 SIGTERM 信号。

通过 kill -l 查看linux支持哪些信号:

常见Java应用如何优雅关闭

linux提供了 signal() api,可以将信号处理函数注册上去:

 
  1. #include <signal.h> 
  2. #include <stdio.h> 
  3. #include <unistd.h> 
  4. #include <stdlib.h> 
  5. #include <stdbool.h> 
  6.  
  7. static void gracefulClose(int sig)   
  8.     printf("执行清理工作\n"); 
  9.     printf("JVM 已关闭\n"); 
  10.     exit(0);    //正常关闭 
  11.  
  12. int main(int argc,char *argv[])   
  13.     if(signal(SIGTERM,gracefulClose) == SIG_ERR) 
  14.         exit(-1); 
  15.  
  16.     printf("JVM 已启动\n"); 
  17.  
  18.     while(true
  19.     { 
  20.         // 执行工作 
  21.         sleep(1); 
  22.     } 

三、Java提供的Shutdown Hook

Java并不支持类似于linux的信号机制,但是提供了 Runtime.addShutdownHook(Thread hook) 的api。

在JVM关闭前,会并发执行各个Hook线程。

 
  1. public class ShutdownHook { 
  2.  
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         Runtime.getRuntime().addShutdownHook(new DbShutdownWork()); 
  5.         System.out.println("JVM 已启动"); 
  6.  
  7.         while(true){ 
  8.             Thread.sleep(10L); 
  9.         } 
  10.     } 
  11.  
  12.     static class DbShutdownWork extends Thread{ 
  13.         public void run(){ 
  14.             System.out.println("关闭数据库连接"); 
  15.         } 
  16.     } 

四、Spring Boot提供的优雅关闭功能

我们一般采用如下的方式,启动一个Spring boot应用:

 
  1. public static void main(String[] args) throws Exception {   
  2.     SpringApplication.run(SampleController.class, args); 

SpringApplication.run()代码如下,会调用到refreshContext(context)方法:

 
  1. public ConfigurableApplicationContext run(String... args) {   
  2.     StopWatch stopWatch = new StopWatch(); 
  3.     stopWatch.start(); 
  4.     ConfigurableApplicationContext context = null
  5.     FailureAnalyzers analyzers = null
  6.     configureHeadlessProperty(); 
  7.     SpringApplicationRunListeners listeners = getRunListeners(args); 
  8.     listeners.started(); 
  9.     try { 
  10.         ApplicationArguments applicationArguments = new DefaultApplicationArguments( 
  11.                 args); 
  12.         ConfigurableEnvironment environment = prepareEnvironment(listeners, 
  13.                 applicationArguments); 
  14.         Banner printedBanner = printBanner(environment); 
  15.         context = createApplicationContext(); 
  16.         analyzers = new FailureAnalyzers(context); 
  17.         prepareContext(context, environment, listeners, applicationArguments, 
  18.                 printedBanner); 
  19.         refreshContext(context); 
  20.         afterRefresh(context, applicationArguments); 
  21.         listeners.finished(context, null); 
  22.         stopWatch.stop(); 
  23.         if (this.logStartupInfo) { 
  24.             new StartupInfoLogger(this.mainApplicationClass) 
  25.                     .logStarted(getApplicationLog(), stopWatch); 
  26.         } 
  27.         return context; 
  28.     } 
  29.     catch (Throwable ex) { 
  30.         handleRunFailure(context, listeners, analyzers, ex); 
  31.         throw new IllegalStateException(ex); 
  32.     } 

refreshContext()方法比较简单:

 
  1. private void refreshContext(ConfigurableApplicationContext context) {   
  2.     refresh(context);   //调用ApplicationContext.refresh() 
  3.     if (this.registerShutdownHook) {        //registerShutdownHook默认值为true 
  4.         try { 
  5.             context.registerShutdownHook(); 
  6.         } 
  7.         catch (AccessControlException ex) { 
  8.             // Not allowed in some environments. 
  9.         } 
  10.     } 

AbstractApplicationContext.registerShutdownHook()代码:

 
  1. public void registerShutdownHook() {   
  2.     if (this.shutdownHook == null) { 
  3.         this.shutdownHook = new Thread() { 
  4.             @Override 
  5.             public void run() { 
  6.                 synchronized (startupShutdownMonitor) { 
  7.                     doClose(); 
  8.                 } 
  9.             } 
  10.         }; 
  11.         Runtime.getRuntime().addShutdownHook(this.shutdownHook); 
  12.     } 

很明显,Spring boot通过在启动时,向JVM注册一个ShutdownHook,从而实现JVM关闭前,正常关闭Spring容器。而Spring在销毁时,会依次调用bean的destroy动作来销毁。

五、Dubbo的优雅关闭策略

Dubbo同样是基于ShutdownHook实现的。

AbstractConfig的static代码:

 
  1. static {   
  2.     Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { 
  3.         public void run() { 
  4.             if (logger.isInfoEnabled()) { 
  5.                 logger.info("Run shutdown hook now."); 
  6.             } 
  7.             ProtocolConfig.destroyAll(); 
  8.         } 
  9.     }, "DubboShutdownHook")); 

六、总结

只要我们的应用运行在linux平台上,所有的优雅关闭方案都是基于linux提供的信号机制提供的,JVM也是如此。

Java并没有为我们提供与之一一对应的api,而是给出了个ShutdownHook机制,也能达到类似的效果,缺点是我们无法得知JVM关闭的原因。

像dubbo、spring boot等成熟的开源框架,都实现了自动注册ShutdownHook的功能,从而避免使用者忘记调用优雅关闭api引发问题,降低框架的使用难度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Java项目中,你可以使用Spring的生命周期回调来实现优雅关闭应用程序。以下是一种常见的方法: 1. 实现`SmartLifecycle`接口:创建一个类并实现`SmartLifecycle`接口,该接口提供了生命周期回调的方法。 ```java import org.springframework.context.SmartLifecycle; public class GracefulShutdown implements SmartLifecycle { private boolean isRunning = false; @Override public void start() { isRunning = true; } @Override public void stop() { isRunning = false; // 执行需要在应用关闭前完成的操作 } @Override public boolean isRunning() { return isRunning; } @Override public int getPhase() { // 设置回调的阶段,越小越早执行 return Integer.MAX_VALUE; } } ``` 2. 注册`GracefulShutdown`为Spring Bean:将`GracefulShutdown`类作为Spring Bean进行注册。 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public GracefulShutdown gracefulShutdown() { return new GracefulShutdown(); } } ``` 3. 配置应用上下文:在Spring配置文件(如`applicationContext.xml`)中加入以下配置,以启用生命周期回调。 ```xml <bean class="com.example.GracefulShutdown" /> ``` 4. 触发应用关闭:当你想要关闭应用程序时,可以通过关闭Spring应用上下文来触发生命周期回调。 ```java import org.springframework.context.ConfigurableApplicationContext; public class ApplicationShutdown { public static void main(String[] args) { ConfigurableApplicationContext context = ... // 获取应用上下文 // 手动关闭应用上下文 context.close(); } } ``` 通过这种方式,你可以在应用关闭前执行一些必要的操作,例如释放资源、保存数据等。在调用`context.close()`关闭应用上下文时,`GracefulShutdown`中的`stop()`方法将被调用,以执行自定义的关闭逻辑。 请注意,`SmartLifecycle`接口还提供了其他可选的回调方法,如`isAutoStartup()`和`stop(Runnable callback)`,你可以根据需求进行实现和使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值