首先来介绍下什么是优雅地停止,简而言之,就是对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响,可以继续完成已有请求的处理,但是停止接受新请求

在 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.tomcatGracefulShutdown 里,大概逻辑就是先停止外部的所有新请求,然后再处理关闭前收到的请求,有兴趣的可以自己去看下。

内嵌的 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-Learngraceful-shutdown 目录下。

最好的关系就是互相成就,大家的在看、转发、留言三连就是我创作的最大动力。

参考

https://github.com/spring-projects/spring-boot/issues/4657

https://github.com/wupeixuan/SpringBoot-Learn

如何优雅地停止 Spring Boot 应用?的更多相关文章

  1. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  2. 如何优雅的设计 Spring Boot API 接口版本号

    原文:https://blog.mariojd.cn/how-to-design-spring-boot-api-version-number-elegantly.html 一般来说,系统上线以后,需 ...

  3. 如何优雅的关闭基于Spring Boot 内嵌 Tomcat 的 Web 应用

    背景 最近在搞云化项目的启动脚本,觉得以往kill方式关闭服务项目太粗暴了,这种kill关闭应用的方式会让当前应用将所有处理中的请求丢弃,响应失败.这种形式的响应失败在处理重要业务逻辑中是要极力避免的 ...

  4. Spring Boot 系列:最新版优雅停机详解

    爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方. 开源项目: 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cub ...

  5. spring boot 服务 正确关闭方式

    引言 Spring Boot,作为Spring框架对“约定优先于配置(Convention Over Configuration)”理念的最佳实践的产物,它能帮助我们很快捷的创建出独立运行.产品级别的 ...

  6. 通过docker-composer启动容器nginx,并完成spring.boot的web站点端口转发

    前面已经讲过2篇基于docker的mysql.redis容器编排并启动.这次将练习下nginx的docker方式的部署,以及通过nginx去代理宿主主机上的Web服务应该怎么配 PS:(这里由于ngi ...

  7. Spring Boot 2.x基础教程:JSR-303实现请求参数校验

    请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点的常见场景.比较常见的问题主要表现在以下几个方面: 仅依靠前端框架解决参数校验,缺失服务端的校验.这种情况常见于需要同时开发前后端的时候,虽然 ...

  8. Kubernetes部署Spring Boot应用

    SpringBoot项目 新建springboot项目 @RestController public class HelloWorldController { @RequestMapping(&quo ...

  9. 在Spring Boot中配置web app

    文章目录 添加依赖 配置端口 配置Context Path 配置错误页面 在程序中停止Spring Boot 配置日志级别 注册Servlet 切换嵌套服务器 在Spring Boot中配置web a ...

随机推荐

  1. Postman学习宝典(三)

    Postman 入门3 - Newman Newman 官方帮助文档地址 Newman 安装 嗯,它需要安装,因为它不是音乐播放器!Newman是为Postman而生,专门用来运行Postman编写好 ...

  2. python实现登录密码重置简易操作

    需求: 1.用户输入密码正确登录 2.用户输入密码错误退出并调用函数继续输入 3.用户输入密码符合原先给定的一个值时,允许用户重置密码,并且可以用新密码登录 4.输入三次后禁止输入 虽然贴别的简单,但 ...

  3. React组件setState

    注意: 1. 自定义组件首字母必须大写.这里是以函数表达式的方式定义子组件的. 2. 使用 ES6 的 class 关键字创建的 React 组件,组件中的方法遵循与常规 ES6 class 相同的语 ...

  4. Vue Element-UI使用第三方icon图标(转)

    转载自:https://www.jianshu.com/p/59dd28f0b9c9 1.打开阿里icon,注册 >登录>图标管理>我的项目   2.新建项目 3. 添加icon到项 ...

  5. SpringBoot注解分析

    Spring boot 简介:是spring社区发布的一个开源项目,旨在帮助开发者更快更简单的构建项目,使用习惯优于配置,的理念让你的项目快速的跑起来,使用springboot可以不用,或者很少的配置 ...

  6. 【C#图解教程学习笔记】第13章 委托

    13.1 什么是委托 委托是持有一个或多个方法的对象,可将一个方法传递到另一个方法. 委托是用户自定义的引用类型. 13.2 委托概述 类表示的是数据和方法的集合,而委托持有一个或多个方法,以及一系列 ...

  7. php实用正则

    1 Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ 2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z ...

  8. 第3章_关系数据库标准语言(SQL)_006_由元组关系演算到SQL Command_001_蕴含式 (其中有对EXISTS的分析)

    前序的链接:元组关系演算 六. 蕴含式 ===>1. 什么是“蕴含式”===>设p.q为两个命题.复合命题“如果p,则q”称为p与q的蕴含式,记作p→q,并称p为蕴含式的前件,q为后件.定 ...

  9. Asp.net core logging 日志

    1 基本概念 Dotnet core 一个重要的特征是 Dependency injection ,中文一般是依赖注入,可以简单理解为一个集合,在应用程序启动时,定义各种具体的实现类型并将其放到集合中 ...

  10. DDD之2领域概念

    图中是暗黑领域,非常牛逼的技能. 背景 DDD中出现的名词: 领域,子领域,核心域,通用域,支撑域,限界上下文,聚合,聚合根,实体,值对象 都是关键概念,但是又比较晦涩,在开始DDD之前,搞清楚这些关 ...