SpringBoot如何优雅关闭(SpringBoot2.3&Spring Boot2.2)
优雅停止&暴力停止
- 暴力停止:像日常开发过程中,测试区或者本地开发时,我们并不会考虑项目关闭带来的影响,只是想最快速的关掉重启,所以用的最多的就是kill -9进行暴力停止服务;kill -9的结果就是强制关闭,不会等待服务释放资源等操作,这也造成了,服务中很多进程无法正常结束。
- 优雅停止:何谓优雅停止,就是等待已有的进程结束之后关闭服务,那么如何实现优雅停止SpringBoot服务?
实现优雅停止
SpringBoot要实现优雅停止,分两种情况一个是SpringBoot版本为2.3.0之前,一种是2.3.0及往后的版本。
- SpringBoot 2.3.0及后续版本
在SpringBoot的ReleaseNotes中我们可以看到,在2.3.0版本,SpringBoot新特性中有一个叫GraceFul shutdown的字样。
意思就是,可以通过配置server.shutdown来实现优雅关闭SpriingBoot服务,支持内嵌的Jetty,Reactor,Netty,Undertow服务器,在配置了优雅停止的情况下关闭服务,服务将不会接收后续请求, 并且会等待宽限期,以便完成当前已有请求。
配置解释:
- server.shutdown : graceful : 表示开启优雅停止
- spring.lifecycle.timeout-per-shutdown-phase : 60 :表示最大等待的宽限时间,超过这个时间还有请求没结束的话,会强制关闭服务。
注意:这里需要注意的是,不能使用kill -9的命令停止服务,不然优雅停止的配置不会生效。
- SpringBoot2.3.0之前的版本
在上述的ReleaseNotes看到,配置的方式实现优雅停止时2.3.0之后才有的功能,那往前的版本怎么办?自己手动写。
@Slf4j @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class NginxTestApplication { public static void main(String[] args) { SpringApplication.run(NginxTestApplication.class, args); } /** * 用于接受 shutdown 事件 */ @Bean public GracefulShutdown gracefulShutdown() { return new GracefulShutdown(); } @Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); tomcat.addConnectorCustomizers(gracefulShutdown()); return tomcat; } /** * 优雅关闭 Spring Boot */ private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> { private volatile Connector connector; //获取tomcat的connector @Override public void customize(Connector connector) { this.connector = connector; } //监听事件 @Override public void onApplicationEvent(ContextClosedEvent contextClosedEvent) { //拒绝停机操作的后续请求 this.connector.pause(); log.info("停止Tomcat connector, 拒绝接收后续请求"); //获取对应线程池并做对应类型判断,true则开始优雅关闭 Executor executor = this.connector.getProtocolHandler().getExecutor(); if (executor instanceof ThreadPoolExecutor) { try { ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor; //开始关闭线程池 threadPoolExecutor.shutdown(); log.info("开始关闭线程池"); //最大宽限时间 int waitTime = 30; //若线程池中有未完成事件,等待完成后关闭,若超过最大宽限时间未完成,强制关闭; //若没有未完成事件,直接关闭 if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) { log.info("Tomcat线程池无法在"+waitTime+"s 内优雅关闭. 强制结束"); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } }
在启动类中加上这些东西就行了,写了很多注释,就不详细解释了。
注意:这里同样需要注意的是,不能使用kill -9的命令停止服务,不然优雅停止不会生效。