用 RuoYi (Spring Boot) 开发了一套访客管理系统,由于要频繁的更新部署,我担心重启会影响到正在处理的请求。所以查了一下 Spring Boot 是否支持 Graceful Shutdown。因为 Spring Boot 这种非常成熟的框架,不能没有这种机制的,所以问题就变成了,如何配置来启用 Graceful Shutdown.
Spring Boot 版本
这个 RuoYi 版本中的 Spring Boot 版本为 2.5.15。 Spring Boot 从 2.3.0 起就已引入
# application.yml
server:
shutdown: graceful # 开启优雅停机,默认为 immediate
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 设置等待在途请求完成的最大超时时间,默认30秒
具体说明:
server.shutdown: graceful—— Spring Boot 2.3.0 引入(2.3 之前只有immediate一种行为,配了也不认)
spring.lifecycle.timeout-per-shutdown-phase—— 同版本引入,Duration类型,默认就是30s
Tomcat 9.0.106 的 NIO/NIO2/APR 连接器都支持 pause() 优雅停机(Tomcat 侧需要 9.0.33+),没有问题。应用用的是内嵌 Tomcat(spring-boot-starter-web),正好是这套配置支持的容器类型。
两个属性的实际语义
| 配置 | 作用 |
|---|---|
server.shutdown: immediate(默认) |
JVM 收到 SIGTERM 直接销毁上下文,在途请求被硬切 |
server.shutdown: graceful |
先暂停 Tomcat 连接器(不再接新连接,新请求返回 503),等在途请求跑完再关上下文 |
spring.lifecycle.timeout-per-shutdown-phase |
同一个值同时用于:① Web 服务器等待在途请求的上限;② DefaultLifecycleProcessor 每个停机 phase 的超时。超时后会打 warn 并强行继续往下走 |
所以不必去 Tomcat 里另找“grace period”属性 —— Spring Boot 就是复用这个 lifecycle 超时作为 Web 容器的等待上限。
怎么验证生效
重启时看日志,正常会出现类似:
o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
实测方式:起一个慢接口(sleep 10s),请求发出后立刻 kill(SIGTERM,不要 -9)进程,观察该请求是否返回正常响应、日志是否等到请求结束后才 Graceful shutdown complete。
如果当前没有线程在处理请求,是否也要等 30 秒才能完成停止进程
不会。30s 是「最长等待上限」,不是「固定等 20 秒」
timeout-per-shutdown-phase 的语义是 deadline,不是 sleep。空闲时它会在毫秒级结束。
它的实际工作机制
Spring Boot 的 GracefulShutdown(org.springframework.boot.web.embedded.tomcat.GracefulShutdown)停机过程是:
- 先关闸:关闭 Connector 的接收,不再接受新连接/新请求(此时新请求被拒绝)。
- 然后轮询:循环检查当前 ServletContext 的活跃请求计数,轮询间隔约 50ms。
- 归零即走:计数一旦为 0,立刻跳出循环,打印
Graceful shutdown complete,继续往下做容器销毁。
- 超时才是保底:把 30s 通过
Connector#setStopTimeout交给它,只有”等满 30s 还有请求没跑完”这种情况,才会强制中断那些请求。
所以没有线程在处理请求时,它压根不进等待循环,第一个轮询周期就判定空闲走了。
空闲停机的真实耗时构成
| 阶段 | 耗时 |
|---|---|
| pause + 活跃计数检查 | ~0 ms(≤ 1 个轮询周期) |
Context 销毁:Quartz shutdown、Druid 连接池 close、Lettuce close、ShutdownManager 等异步池(空闲立即返回) |
通常 0.x ~ 2~3 s |
| 合计 | 一般 1~3 秒,与 30s 无关 |
日志上表现为 Commencing graceful shutdown... 和 Graceful shutdown complete 两行时间戳几乎相同。
一个常见的误会:空闲 keep-alive 连接不会被等
浏览器 / 微信 webview 持有的长连接不算”活跃请求”,计数只统计正在被 Servlet 处理的请求。所以不会因为有几十个空闲长连接就多等——Tomcat 关 Connector 时会把它们直接关掉。