SpringBoot 优雅停服

一、介绍

当外部流量请求到服务端接口执行业务逻辑的时候,若服务端此时执行关机 (kill),spring boot 默认情况会直接关闭容器(tomcat 等),导致此业务逻辑执行失败。在一些业务场景下:会出现数据不一致的情况,事务逻辑不会回滚。

在最新的 spring boot 2.3 版本,内置此功能,不需要再自行扩展容器线程池来处理, 目前 spring boot 嵌入式支持的 web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反应式和基于 Servlet 的 web 应用程序都支持优雅停机功能。

持的 shutdown 行为,我们看下源码枚举如下:

复制代码
public enum Shutdown { 
/**  
* 优雅停机 (限期停机)  
*/ 
GRACEFUL, 
/**  
* 立即停机  
*/ 
IMMEDIATE;
}

二、使用

当使用server.shutdown=graceful启用时,在 web 容器关闭时,web 服务器将不再接收新请求,并将等待活动请求完成的缓冲期。

缓冲期 timeout-per-shutdown-phase 配置:默认时间为 30S, 意味着最大等待 30S,超时候无论线程任务是否执行完毕都会停机处理,一定要根据项目实际需要合理设置。

复制代码
spring:
  lifecycle:
    timeout-per-shutdown-phase: 10s   # 留给Web容器处理存量请求的最大时间

server:
  shutdown: graceful

三、源码分析

内置 Tomcat 启动流程

  1. 自动配置入口

通过 EmbeddedWebServerFactoryCustomizerAutoConfiguration 引入 Tomcat 相关组件:

复制代码
@Configuration
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
    
    @Bean
    public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(
            Environment environment, ServerProperties serverProperties) {
        // 创建 Tomcat 配置定制器
        return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
    }
}
  1. 核心组件职责
组件 作用
TomcatWebServerFactoryCustomizer 将 ServerProperties 中的配置应用到 Tomcat 容器
WebServerFactoryCustomizerBeanPostProcessor 后置处理器,在 Bean 初始化前获取所有定制器并应用到 Factory
ServletWebServerApplicationContext 重写 onRefresh() 方法,调用 createWebServer() 创建容器
TomcatServletWebServerFactory 负责实际创建 Tomcat 实例并获取 WebServer
  1. 启动调用链

    SpringApplication.run()
    ↓
    AbstractApplicationContext.refresh()
    ↓
    ServletWebServerApplicationContext.onRefresh() [模板方法]
    ↓
    createWebServer() → Factory.getWebServer() → Tomcat 启动


优雅停机机制(Graceful Shutdown)

触发阶段:

  1. 接收信号 :kill -15 (SIGTERM),不是 kill -9( kill -9,暴力美学强制杀死进程,不会执行 ShutdownHook**)**

  2. JVM ShutdownHook :SpringApplicationShutdownHook 在 run() 前注册到 JVM

  3. 关闭上下文 :调用 ApplicationContext.close(),触发 ContextClosedEvent

Tomcat 处置阶段(GracefulShutdown):

复制代码
final class GracefulShutdown {
    void shutDownGracefully(GracefulShutdownCallback callback) {
        // 1. 暂停 Connector,停止接收新请求
        connectors.forEach(this::close);
        
        // 2. 循环等待活动请求完成
        while (isActive(context)) {
            if (超时或中断) {
                callback.shutdownComplete(REQUESTS_ACTIVE);  // 强制结束
                return;
            }
            Thread.sleep(50);
        }
        
        callback.shutdownComplete(IDLE);  // 优雅结束
    }
    
    private void close(Connector connector) {
        connector.pause();  // 暂停新连接
        connector.getProtocolHandler().closeServerSocketGraceful();  // 优雅关闭Socket
    }
}

执行顺序:

  1. 暂停 Connector → 不再接收新 HTTP 请求

  2. 等待活跃请求 → 在 timeout-per-shutdown-phase 时间内完成

  3. 超时处理 → 超时强制关闭剩余连接

  4. Bean 销毁 → 触发 @PreDestroy 和 DisposableBean

内嵌 Tomcat 如何关闭(优雅停机)-源码分析

当接收到终止信号时,经过一连串的调用,最终会调用到 AbstractApplicationContext#doClose 方法。

复制代码
AbstractApplicationContext#doClose()
    ↓
DefaultLifecycleProcessor#onClose
    ↓
DefaultLifecycleProcessor#stopBeans
    ↓
DefaultLifecycleProcessor#stop()
    ↓
DefaultLifecycleProcessor#doStop()
    ↓
WebServerGracefulShutdownLifecycle#stop()
    ↓
TomcatWebServer#shutDownGracefully()
    ↓
WebServerStartStopLifecycle.stop()
    ↓
TomcatWebServer#stop()

当开启优雅停机时,控制台输出如下,会等待所有请求完成,然后进行 shutdown。

复制代码
[ionShutdownHook] o.s.b.w.e.tomcat.Gracefulshhutdown : Commencing graceful shutdown. Waiting for activerequests to complete
[tomcat-shutdown] o.s.b.w.e.tomcat.Gracefulshutdown: Graceful shutdown complete

当优雅停机的处理完毕后,接着会处理 WebServerStartStopLifecycle 的 stop 操作,最终会调用 Tomcat API 完成服务的停止。

Spring Boot 内嵌的 Tomcat 启动、关闭的实现方式,大体可以简化成:

内嵌容器启动或关闭时,经过 DefaultLifecycleProcessor 调用一系列方法进行最终是由 WebServerStartStopLifecycle、 WebServerGracefulShutdownLifecycle 来处理。而WebServerStartStopLifecycle、 WebServerGracefulShutdownLifecycle 持有 webServer 对象,最后调用持有的 xxWebServer 对象的 start、stop 等方法来完成容器的启停操作。

相关推荐
Mikko73 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
计算机毕设定制辅导-无忧学长8 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
vipxieliang9 小时前
ValidX 正则表达式预编译优化:Pattern 复用策略
java·spring boot·后端
余槐i10 小时前
Spring Boot 3.2 偶发500无日志:线程池复用导致请求上下文丢失
spring boot·redis·后端·mysql·性能优化
杨凯凡10 小时前
Spring Boot 的 @Transactional 为什么会失效?从代理、自调用到异常回滚的排查实战
java·spring boot·spring·事务·后端开发
梅孔立12 小时前
Docker 容器网络不通:Tomcat 无法解析 MySQL、Nginx 无法反代 Tomcat 的生产级排查与治理
网络·docker·tomcat
云创智城-yuncitys1 天前
YunParking 4.0:JDK 17 与 Spring Boot 3.2 的停车云升级
java·开发语言·spring boot
paopaokaka_luck1 天前
灾害应急物资管理系统(AI应急助手、Echarts可视化数据看板、捐赠入库联动、 物资全流程管理、库存变动追溯、灾情资讯与互动反馈、物资发放公示)
java·前端·javascript·spring boot·mysql·tomcat·echarts
学长毕设1 天前
基于SpringBoot的网上流浪狗救助捐赠平台-附源码
java·spring boot·后端
码农的日常1 天前
前后端Long精度丢失的踩坑
spring boot·架构