赞
踩
首先,我们先了解什么叫优雅关闭?
第一步:停止接受请求和内部线程
第二步:判断是否有线程正在执行
第三步:等待正在执行的线程执行完毕
第四步:停止服务容器
使用 kill -9 pid ???
NO! 暴力停止可能会带来严重的比如事务问题。
kill -15 pid 关闭
kill -15 这个命令可以理解为操作系统发送一个通知告诉应用主动关闭.,会让程序马上调用线程的interrupt方法,目的是为了让线程停止。
ConfigurableApplicationContext.colse() 方法关闭
ConfigurableApplicationContext cyx = (ConfigurableApplicationContext) context;
cyx.close();
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
}
}
}
}
查看源码可知,程序在启动的时候向jvm注册了一个关闭钩子,我们在执行colse方法的时候会移除这个关闭钩子,jvm就会知道这是需要停止服务。
这种方式也触发了线程的interrupt方法导致线程报错,原理和kill -15差不多。
actuator 关闭
server:
port: 9988
management:
endpoints:
web:
exposure:
include: shutdown
endpoint:
shutdown:
enabled: true
server:
port: 8888
调用shutdown:
这种方式原理依然是调用了线程的interrupt方法。
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(); } } } }
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; } }
再次测试关闭程序,发现没有线程中断的报错了,因为程序等待了一段时间再结束的线程池。这个时间就是配置的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("正在备份数据。。。。。。。。。。。");
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。