Spring Boot 优雅停机配置详解(含 Docker / Docker Swarm)

一、配置说明

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

或者 application.properties 格式:

server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
配置项作用
server.shutdown=graceful开启优雅停机,让应用在停止时,不再接收新请求,新请求被gateway分配到集群其他实例上,本实例等待正在处理的请求完成后重启
spring.lifecycle.timeout-per-shutdown-phase=30s设置每个停机阶段的超时时间上限,默认就是 30s

二、核心机制

spring.lifecycle.timeout-per-shutdown-phase=30s 的含义:

  • 30s 是硬时限(Hard Deadline):最多等待 30 秒
  • 提前完成则立即继续:如果所有请求在 10 秒内处理完成,不会傻等剩余的 20 秒,会立即继续关闭流程
  • 超时强制终止:如果 30 秒到了还有未完成的请求,Spring 会强制终止这些请求,然后继续关闭应用

日志示例

正常完成:

INFO  o.s.b.w.e.tomcat.GracefulShutdown  : Graceful shutdown complete in 10s

超时强制终止:

INFO  o.s.b.w.e.tomcat.GracefulShutdown  : Graceful shutdown timed out after 30s. Active requests: 3

三、IDEA 本地调试效果

注意:本地调试时,如果使用强制停止按钮,优雅停机配置不会生效。

本地验证方法

编写一个慢请求接口:

@RestController
public class TestController {
    @GetMapping("/slow")
    public String slow() throws InterruptedException {
        Thread.sleep(10000); // 10秒慢请求
        return "done";
    }
}
  1. 请求 http://localhost:8080/slow
  2. 立即在 IDEA 中点击红色停止按钮
  3. 观察日志 —— 加了配置会等待请求完成,没加会立即中断

四、各版本默认值变化

Spring Boot 版本server.shutdown 默认值是否需要配置启用优雅停机?
2.3.0 至 3.3.ximmediate需要。手动配置 server.shutdown=graceful
3.4.0 及之后(含 4.0)graceful不需要。此功能已默认启用

重要:优雅停机功能从 Spring Boot 2.3.0 开始引入,但默认开启是从 Spring Boot 3.4.0 版本开始的。


五、Docker 环境配置

5.1 Docker 容器的停止信号

Docker 容器默认使用 SIGTERM 信号来停止容器。Spring Boot 应用会捕获 SIGTERM 信号并触发优雅停机流程。

# 停止容器(默认发送 SIGTERM)
docker stop <container_name>

# 强制停止(发送 SIGKILL,优雅停机不会生效)
docker kill <container_name>

5.2 Dockerfile 最佳实践

FROM openjdk:17-jdk-slim

# 使用 JAR 包方式
COPY target/app.jar /app.jar

# 关键:使用 exec 形式启动,确保应用能正确接收 SIGTERM 信号
ENTRYPOINT ["java", "-jar", "/app.jar"]

# 或者使用完整 JVM 参数(推荐)
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "/app.jar"]

重要:ENTRYPOINT 必须使用 exec 形式(中括号方式),而不能使用 shell 形式。shell 形式不会直接转发信号给 Java 进程。

# ❌ 错误写法:shell 形式,信号不会被转发
ENTRYPOINT java -jar /app.jar

# ✅ 正确写法:exec 形式,信号正确转发
ENTRYPOINT ["java", "-jar", "/app.jar"]

5.3 docker-compose 配置

version: '3.8'

services:
  my-app:
    image: my-app:latest
    ports:
      - "8080:8080"
    stop_grace_period: 45s        # 必须大于 spring.lifecycle.timeout-per-shutdown-phase
    restart: always
    environment:
      - SERVER_SHUTDOWN=graceful
      - SPRING_LIFECYCLE_TIMEOUT-PER-SHUTDOWN-PHASE=30s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

5.4 时间参数关系图(Docker)

是

否

Docker 发送 SIGTERM

等待应用处理...

应用是否在
10s 内完成?

应用完成优雅停机

Docker 立即停止容器
不等剩余时间

30s 后 Spring timeout 到了

Spring 强制终止
容器继续运行

45s 后 Docker stop_grace_period 到了

Docker 发送 SIGKILL

强制杀死容器


六、Docker Swarm 环境配置

6.1 Swarm 服务的优雅停机

Docker Swarm 在滚动更新或服务缩容时,会按照以下流程操作:

  1. 将旧容器从负载均衡中摘除(不再接收新请求)
  2. 发送 SIGTERM 信号给容器
  3. 等待容器自行退出

6.2 docker stack deploy 配置

version: '3.8'

services:
  my-app:
    image: my-app:latest
    ports:
      - target: 8080
        published: 8080
        mode: host
    stop_grace_period: 45s
    restart_policy:
      condition: any
      delay: 5s
      max_attempts: 3
      window: 120s
    update_config:
      parallelism: 1                # 每次更新 1 个副本
      delay: 10s                    # 更新间隔
      failure_action: rollback
      monitor: 30s
      max_failure_ratio: 0.3
      order: start-first            # 先启动新容器再停止旧容器
    environment:
      - SERVER_SHUTDOWN=graceful
      - SPRING_LIFECYCLE_TIMEOUT-PER-SHUTDOWN-PHASE=30s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
      restart_policy:
        condition: any

6.3 Swarm 滚动更新流程

1. Swarm Manager 调度新容器启动
   ↓
2. 新容器通过健康检查后,开始接收流量
   ↓
3. Swarm 将旧容器从 routing mesh 中摘除
   ↓
4. 旧容器收到 SIGTERM 信号,触发优雅停机
   ↓
5. 等待 30s(Spring timeout)
   ├── 请求完成 → 正常退出
   └── 超时未完成 → 强制终止
   ↓
6. 如果 45s(stop_grace_period)后旧容器仍未退出
   → Swarm 发送 SIGKILL 强制杀死
   ↓
7. 继续更新下一个副本

6.4 Swarm 关键配置参数说明

参数建议值说明
stop_grace_period45s - 60s必须 > spring.lifecycle.timeout-per-shutdown-phase
update_config.parallelism1每次更新 1 个副本,确保服务可用性
update_config.delay10s更新间隔,给新容器足够时间启动
update_config.orderstart-first先启动新容器再停旧容器,实现零停机
healthcheck.start_period30s给应用足够的启动时间,避免过早检查失败

七、生产环境推荐配置汇总

7.1 时间参数配置

环境Spring timeoutDocker/Swarm stop_grace_period说明
一般 Web 应用30s(默认)45s留 15s 缓冲
有长任务60s - 120s90s - 150s给慢请求更多时间
高并发/敏感15s - 20s30s快速失败,让流量切到其他实例

7.2 完整配置清单

application.yml:

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Dockerfile:

FROM openjdk:17-jdk-slim
COPY target/app.jar /app.jar
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "/app.jar"]

docker-compose.yml:

services:
  my-app:
    image: my-app:latest
    stop_grace_period: 45s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

Swarm 服务配置(docker-stack.yml):

services:
  my-app:
    image: my-app:latest
    stop_grace_period: 45s
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
        monitor: 30s

八、常见问题排查

8.1 优雅停机不生效

  1. 检查 ENTRYPOINT 是否使用 exec 形式:

    • 使用 ["java", "-jar"] ✅
    • 不要使用 java -jar ❌
  2. 检查 server.shutdown 是否配置为 graceful(Spring Boot 3.4.0 之前)

  3. 检查是否使用了强制停止命令:

    • docker kill 不会触发优雅停机
    • 应使用 docker stop 或 docker service update

8.2 容器被提前强制杀死

# 查看容器日志,确认是否为超时被 kill
docker logs <container_name> | grep -i "shutdown\|timeout\|kill"

# 检查 Docker stop_grace_period 是否小于 Spring timeout
docker inspect <container_name> | grep -i "stopgraceperiod"

解决:增大 stop_grace_period,确保大于 spring.lifecycle.timeout-per-shutdown-phase。

8.3 滚动更新期间出现 5xx 错误

  1. 增加 healthcheck.start_period,给新容器足够时间启动
  2. 增加 update_config.delay,让流量有足够时间切换
  3. 检查 Spring Boot 的 management.server.port 是否独立,避免健康检查端口与应用端口冲突

九、完整停机流程示意图

是 情况A

否 情况B

超时兜底

触发停止信号 docker stop / Swarm 更新

Swarm Routing Mesh 将容器从负载均衡摘除 不再接收新请求

向容器发送 SIGTERM 信号

Spring Boot 捕获信号 开始优雅停机流程

等待正在处理的请求完成 计时开始

30s内请求是否完成

所有请求在10s内完成

立即继续关闭流程 不等剩余时间

30s到 仍有请求未完成 Spring timeout到期

Spring 强制终止未完成请求

关闭 Spring 容器 销毁Bean 释放资源

应用进程正常退出

容器状态 Exited 0 优雅停机成功

45s后容器仍未退出 Docker stop_grace_period到期

Docker Swarm 发送 SIGKILL

强制杀死容器

容器状态 Exited 137 被强制杀死


十、总结

关键点说明
Spring timeout最多等待请求完成的时限(默认 30s)
Docker stop_grace_periodDocker 等待容器退出的总时限,必须 > Spring timeout
信号传递必须使用 exec 形式的 ENTRYPOINT,确保 SIGTERM 正确传递
滚动更新Swarm 的 start-first 策略可实现零停机部署
健康检查配置合理的健康检查,确保新容器正常后才接入流量
超时关系K8s terminationGracePeriodSeconds > Docker stop_grace_period > Spring timeout

一句话总结:30s 是 Spring 等待请求完成的"硬上限";Docker/Swarm 的 stop_grace_period 是容器等待退出的"总上限",必须大于 Spring 的 timeout,并留出 10-15s 的缓冲时间,同时确保 ENTRYPOINT 使用 exec 形式传递信号,才能实现真正的优雅停机和无损发布。

更多推荐