当前位置:   article > 正文

如何优雅的关闭SpringBoot程序服务

优雅的关闭springboot

首先,我们先了解什么叫优雅关闭?

第一步:停止接受请求和内部线程
第二步:判断是否有线程正在执行
第三步:等待正在执行的线程执行完毕
第四步:停止服务容器

使用 kill -9 pid ???
NO! 暴力停止可能会带来严重的比如事务问题。


方式一:

kill -15 pid 关闭

kill -15 这个命令可以理解为操作系统发送一个通知告诉应用主动关闭.,会让程序马上调用线程的interrupt方法,目的是为了让线程停止。


方式二:

ConfigurableApplicationContext.colse() 方法关闭

ConfigurableApplicationContext cyx = (ConfigurableApplicationContext) context;
cyx.close();
  • 1
  • 2

close()方法源码:

public void close() {
	synchronized (this.startupShutdownMonitor) {
		doClose();
		// If we registered a JVM shutdown hook, we don't need it anymore now:
		// We've already explicitly closed the context.
		if (this.shutdownHook != null) {
			try {
				Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
			}
			catch (IllegalStateException ex) {
				// ignore - VM is already shutting down
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

查看源码可知,程序在启动的时候向jvm注册了一个关闭钩子,我们在执行colse方法的时候会移除这个关闭钩子,jvm就会知道这是需要停止服务。

这种方式也触发了线程的interrupt方法导致线程报错,原理和kill -15差不多。


方式三:

actuator 关闭

server:
  port: 9988

management:
  endpoints:
    web:
      exposure:
        include: shutdown
  endpoint:
    shutdown:
      enabled: true
  server:
    port: 8888
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

调用shutdown:
在这里插入图片描述
这种方式原理依然是调用了线程的interrupt方法。


那么如何在优雅停止服务的时候做更多事情?

比如延迟10s再停止服务:

1、新增停止springboot服务类:ElegantShutdownConfig.java

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Configuration
public class ElegantShutdownConfig implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {

    private volatile Connector connector;
    private final int waitTime = 5;

    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        connector.pause();
        Executor executor = connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
            try {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                    log.info("请尝试暴力关闭");
                }
                log.info("程序正常关闭");
            } catch (InterruptedException ex) {
                log.info("程序关闭异常了");
                Thread.currentThread().interrupt();
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

2、在启动类中加入bean

import com.ymy.config.ElegantShutdownConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ShutdownServerApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(ShutdownServerApplication.class, args);
        run.registerShutdownHook();
    }

    @Bean
    public ElegantShutdownConfig elegantShutdownConfig() {
        return new ElegantShutdownConfig();
    }

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addConnectorCustomizers(elegantShutdownConfig());
        return tomcat;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

再次测试关闭程序,发现没有线程中断的报错了,因为程序等待了一段时间再结束的线程池。这个时间就是配置的waitTime。
那可能你会有疑问了,jvm没有立即停止,那这个时候在有请求会发生什么呢?如果关闭的时候有新的请求,服务将不再接收此请求。


又比如停止服务之前做点什么操作:

其实很简单在你要执行的方法上添加一个注解即可:@PreDestroy

新增服务停止备份工具类:DataBackupConfig.java

import org.springframework.context.annotation.Configuration;

import javax.annotation.PreDestroy;

@Configuration
public class DataBackupConfig {

    @PreDestroy
    public  void backData(){
        // 可以在这里做数据备份,也可以记录停机时间等。
        System.out.println("正在备份数据。。。。。。。。。。。");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/334255
推荐阅读
相关标签
  

闽ICP备14008679号