如何优雅地停止 Spring Boot 应用?
首先来介绍下什么是优雅地停止,简而言之,就是对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响,可以继续完成已有请求的处理,但是停止接受新请求。
在 Spring Boot 2.3 中增加了新特性优雅停止,目前 Spring Boot 内置的四个嵌入式 Web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反应式和基于 Servlet 的 Web 应用程序都支持优雅停止。
下面,我们先用新版本尝试下:
Spring Boot 2.3 优雅停止
首先创建一个 Spring Boot 的 Web 项目,版本选择 2.3.0.RELEASE,Spring Boot 2.3.0.RELEASE 版本内置的 Tomcat 为 9.0.35。
然后需要在 application.yml 中添加一些配置来启用优雅停止的功能:
# 开启优雅停止 Web 容器,默认为 IMMEDIATE:立即停止
server:
shutdown: graceful
# 最大等待时间
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
其中,平滑关闭内置的 Web 容器(以 Tomcat 为例)的入口代码在 org.springframework.boot.web.embedded.tomcat 的 GracefulShutdown 里,大概逻辑就是先停止外部的所有新请求,然后再处理关闭前收到的请求,有兴趣的可以自己去看下。
内嵌的 Tomcat 容器平滑关闭的配置已经完成了,那么如何优雅关闭 Spring 容器了,就需要 Actuator 来实现 Spring 容器的关闭了。
然后加入 actuator 依赖,依赖如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后接着再添加一些配置来暴露 actuator 的 shutdown 接口:
# 暴露 shutdown 接口
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
其中通过 Actuator 关闭 Spring 容器的入口代码在 org.springframework.boot.actuate.context 包下 ShutdownEndpoint 类中,主要的就是执行 doClose() 方法关闭并销毁 applicationContext,有兴趣的可以自己去看下。
配置搞定后,然后在 controller 包下创建一个 WorkController 类,并有一个 work 方法,用来模拟复杂业务耗时处理流程,具体代码如下:
@RestController
public class WorkController {
@GetMapping("/work")
public String work() throws InterruptedException {
// 模拟复杂业务耗时处理流程
Thread.sleep(10 * 1000L);
return "success";
}
}
然后,我们启动项目,先用 Postman 请求 http://localhost:8080/work 处理业务:

然后在这个时候,调用 http://localhost:8080/actuator/shutdown 就可以执行优雅地停止,返回结果如下:
{
"message": "Shutting down, bye..."
}
如果在这个时候,发起新的请求 http://localhost:8080/work,会没有反应:

再回头看第一个请求,返回了结果:success。
其中有几条服务日志如下:
2020-05-20 23:05:15.163 INFO 102724 --- [ Thread-253] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2020-05-20 23:05:15.287 INFO 102724 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
2020-05-20 23:05:15.295 INFO 102724 --- [ Thread-253] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
从日志中也可以看出来,当调用 shutdown 接口的时候,会先等待请求处理完毕后再优雅地停止。
到此为止,Spring Boot 2.3 的优雅关闭就讲解完了,是不是很简单呢?如果是在之前不支持优雅关闭的版本如何去做呢?
Spring Boot 旧版本优雅停止
在这里介绍 GitHub 上 issue 里 Spring Boot 开发者提供的一种方案:
选取的 Spring Boot 版本为 2.2.6.RELEASE,首先要实现 TomcatConnectorCustomizer 接口,该接口是自定义 Connector 的回调接口:
@FunctionalInterface
public interface TomcatConnectorCustomizer {
void customize(Connector connector);
}
除了定制 Connector 的行为,还要实现 ApplicationListener<ContextClosedEvent> 接口,因为要监听 Spring 容器的关闭事件,即当前的 ApplicationContext 执行 close() 方法,这样我们就可以在请求处理完毕后进行 Tomcat 线程池的关闭,具体的实现代码如下:
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
有了定制的 Connector 回调,还需要在启动过程中添加到内嵌的 Tomcat 容器中,然后等待监听到关闭指令时执行,addConnectorCustomizers 方法可以把定制的 Connector 行为添加到内嵌的 Tomcat 中,具体代码如下:
@Bean
public ConfigurableServletWebServerFactory tomcatCustomizer() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown());
return factory;
}
到此为止,内置的 Tomcat 容器平滑关闭的操作就完成了,Spring 容器优雅停止上面已经说过了,再次就不再赘述了。
通过测试,同样可以达到上面那样优雅停止的效果。
总结
本文主要讲解了 Spring Boot 2.3 版本和旧版本的优雅停止,避免强制停止导致正在处理的业务逻辑会被中断,进而导致产生业务异常的情形。
另外使用 Actuator 的同时要注意安全问题,比如可以通过引入 security 依赖,打开安全限制并进行身份验证,设置单独的 Actuator 管理端口并配置只对内网开放等。
本文的完整代码在 https://github.com/wupeixuan/SpringBoot-Learn 的 graceful-shutdown 目录下。
最好的关系就是互相成就,大家的在看、转发、留言三连就是我创作的最大动力。
参考
如何优雅地停止 Spring Boot 应用?的更多相关文章
- 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版
欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...
- 如何优雅的设计 Spring Boot API 接口版本号
原文:https://blog.mariojd.cn/how-to-design-spring-boot-api-version-number-elegantly.html 一般来说,系统上线以后,需 ...
- 如何优雅的关闭基于Spring Boot 内嵌 Tomcat 的 Web 应用
背景 最近在搞云化项目的启动脚本,觉得以往kill方式关闭服务项目太粗暴了,这种kill关闭应用的方式会让当前应用将所有处理中的请求丢弃,响应失败.这种形式的响应失败在处理重要业务逻辑中是要极力避免的 ...
- Spring Boot 系列:最新版优雅停机详解
爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方. 开源项目: 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cub ...
- spring boot 服务 正确关闭方式
引言 Spring Boot,作为Spring框架对“约定优先于配置(Convention Over Configuration)”理念的最佳实践的产物,它能帮助我们很快捷的创建出独立运行.产品级别的 ...
- 通过docker-composer启动容器nginx,并完成spring.boot的web站点端口转发
前面已经讲过2篇基于docker的mysql.redis容器编排并启动.这次将练习下nginx的docker方式的部署,以及通过nginx去代理宿主主机上的Web服务应该怎么配 PS:(这里由于ngi ...
- Spring Boot 2.x基础教程:JSR-303实现请求参数校验
请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点的常见场景.比较常见的问题主要表现在以下几个方面: 仅依靠前端框架解决参数校验,缺失服务端的校验.这种情况常见于需要同时开发前后端的时候,虽然 ...
- Kubernetes部署Spring Boot应用
SpringBoot项目 新建springboot项目 @RestController public class HelloWorldController { @RequestMapping(&quo ...
- 在Spring Boot中配置web app
文章目录 添加依赖 配置端口 配置Context Path 配置错误页面 在程序中停止Spring Boot 配置日志级别 注册Servlet 切换嵌套服务器 在Spring Boot中配置web a ...
随机推荐
- Java并发编程:线程和锁的使用与解析
线程的使用 新建线程 新建一个线程有两种方法:继承Thread类,然后重写run方法:实现Runnable接口,然后实现run方法.实际上Thread类也是实现的Runnable接口,再加上类只能单 ...
- 00003-aspose for java 生成水印刻章等,可转为word,pic,pdf
对应java代码: package com.yoooya.ytp.utils.doc; import com.aspose.words.Document; import com.aspose.word ...
- [A*算法]基于Unity实现A*算法(二)
写在前面:上一篇当时是非常简单的了解一下A*,昨天还有一些问题没解决,就暂时把自己查阅的文坛摘抄了过来(毕竟人家写的比我要好的多 :> ) 今天终于解决了,就又写了这一篇,正好我自己再梳理一遍, ...
- mysql 一台服务器中装两个mysql
个人经验: 服务器中已有mysql5.0 现要安装mysql5.5 下载安装包,安装后,mysql5.5中没有my.ini文件,就在我自己的电脑上复制了mysql5.5的my.ini文件进去. 1.在 ...
- Maven+JSP+Servlet+C3P0+Mysql实现的音乐库管理系统
项目简介 项目来源于:https://gitee.com/sunnyandgood/OnlineMusic 本系统基于Maven+JSP+Servlet+C3P0+Mysql实现的音乐库管理系统.简单 ...
- ShoneSharp语言(S#)的设计和使用介绍系列(6)— 字符串String
ShoneSharp语言(S#)的设计和使用介绍 系列(6)— 字符串String 作者:Shone 声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneS ...
- 仿开源框架从零到一完整实现高性能、可扩展的RPC框架 | 6个月做成教程免费送
去年年就在写一本付费小册,今年年初基本上就写完了,本来预计计划是春节上线结果由于平台的原因一直拖着没上.五一前跟平台联系给的反馈是五月份能上,结果平台又在重构,停止小册的申请和上线,最后我考虑了一下决 ...
- el-select检索功能
使用element-UI框架的使用,我们经常使用el-select下拉框,很多时候还需要使用可搜索的下拉框,然后elementUI官网的实例中只是提了一下filter-method可以自定义搜索方法, ...
- let面试题
两次输出结构都是2 0 1
- 项目readme文件目录生成工具 treer
生成目录的工具呢有tree和treer,但是tree不知道怎么忽略node_modules文件夹, 而treer就简单了,下面就是基本的命令了 其中-i是指忽略xxx, -e是指导出 安装 npm i ...