avatar

Ryan's Blog

The first step is always the hardest.

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于
  • 工具
Home Spring Boot 优雅停机实践:Graceful Shutdown、Actuator 与 Kubernetes 发布场景
文章

Spring Boot 优雅停机实践:Graceful Shutdown、Actuator 与 Kubernetes 发布场景

Posted 2021-11-25 Updated 3 days ago
By Ryan Chen
21~27 min read

优雅停机(Graceful Shutdown)指应用收到停止信号后,不是立即中断进程,而是先停止接收新请求,等待已经进入应用的请求、任务或资源释放完成,再关闭进程。

在微服务发布、容器重启、Kubernetes 滚动更新、扩缩容和故障迁移场景中,优雅停机非常重要。否则应用可能出现:

  • 请求处理中断。
  • 事务执行一半失败。
  • 消息消费重复或丢失。
  • 线程池任务被强制终止。
  • 上游仍然把流量打到即将关闭的实例。

这篇文章整理 Spring Boot 优雅停机的配置方式、关闭流程、Actuator shutdown 端点、Docker / Kubernetes 场景,以及线程池、定时任务、消息消费者等业务资源的处理建议。

什么是优雅停机

一个理想的关闭流程通常是:

  1. 应用收到停止信号,例如 SIGTERM。
  2. 应用从注册中心或负载均衡中摘除,或者 readiness 变为不可用。
  3. 停止接收新请求。
  4. 等待正在处理的请求完成。
  5. 停止异步任务、定时任务和消息消费者。
  6. 关闭数据库连接池、HTTP 客户端、线程池等资源。
  7. 应用进程退出。

优雅停机的核心不是“永远等下去”,而是在一个可控的时间窗口内尽量完成已有工作,超时后仍要退出。

Spring Boot 内置 Graceful Shutdown

Spring Boot 2.3 开始提供内置 Web Server 优雅停机能力。当前 Spring Boot 版本仍然支持通过配置开启:

server:
  shutdown: graceful

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

配置说明:

配置 说明
server.shutdown=graceful 开启优雅停机
server.shutdown=immediate 立即停机
spring.lifecycle.timeout-per-shutdown-phase 每个关闭阶段等待超时时间

优雅停机会作为关闭 ApplicationContext 的一部分执行,并在较早阶段停止 Web Server 接收新请求。

不同 Web Server 的行为

Spring Boot 支持多种嵌入式 Web Server。优雅停机时,不同 Server 对新请求的处理方式可能不同:

Web Server 行为
Tomcat 停止在网络层接收新请求
Jetty 停止接收新请求
Reactor Netty 停止接收新请求
Undertow 新请求可能直接返回 503

因此,验证优雅停机时要结合实际使用的 Web Server。

关闭信号

Linux / Docker / Kubernetes 中常见信号:

信号 说明
SIGTERM / kill -15 请求进程正常终止,推荐
SIGINT / kill -2 类似 Ctrl + C
SIGKILL / kill -9 强制杀死进程,不会执行 ShutdownHook

推荐使用:

kill -15 <pid>

不要用 kill -9 验证优雅停机,因为它不会给 JVM 执行关闭钩子的机会。

验证优雅停机

启动应用后,执行:

kill -15 <pid>

典型日志类似:

Commencing graceful shutdown. Waiting for active requests to complete
Graceful shutdown complete

验证方法:

  1. 准备一个执行时间较长的接口,例如 sleep 10 秒。
  2. 请求进入后立即发送 SIGTERM。
  3. 观察请求是否能正常返回。
  4. 再发起新请求,观察是否被拒绝或无法建立连接。
  5. 查看应用是否在超时时间内退出。

Actuator shutdown endpoint

Spring Boot Actuator 提供 /actuator/shutdown 端点,但默认是禁用的。开启方式:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: health,info,shutdown

调用:

curl -X POST http://localhost:8080/actuator/shutdown

返回示例:

{
  "message": "Shutting down, bye..."
}

强烈注意:shutdown 端点非常敏感,不建议暴露到公网。生产环境如果确实要使用,应满足:

  • 只开放在管理网络。
  • 配合 Spring Security 认证授权。
  • 配合防火墙或网关 IP 白名单。
  • 有操作审计。
  • 优先使用平台级停止机制,例如 systemd、Docker、Kubernetes。

Docker 场景

Docker 停止容器时,默认会向容器主进程发送 SIGTERM,等待一段时间后再发送 SIGKILL。

常见命令:

docker stop --time 30 <container>

建议:

  • 确保 Java 进程是容器主进程,能够收到 SIGTERM。
  • 不要用脚本吞掉信号。
  • Docker stop timeout 要大于 Spring Boot graceful shutdown timeout。
  • 应用内超时时间、容器停止时间和网关超时时间要协调。

Kubernetes 场景

Kubernetes 删除 Pod 时,通常会:

  1. 将 Pod 标记为 Terminating。
  2. 执行 preStop hook。
  3. 发送 SIGTERM 给容器主进程。
  4. 等待 terminationGracePeriodSeconds。
  5. 超时后发送 SIGKILL。

示例:

spec:
  terminationGracePeriodSeconds: 60
  containers:
    - name: app
      image: example/app:1.0.0
      lifecycle:
        preStop:
          exec:
            command: ["sh", "-c", "sleep 10"]

preStop sleep 的作用通常是给服务发现、负载均衡或网关一点时间停止转发新流量。

建议:

  • terminationGracePeriodSeconds 大于应用最长优雅关闭时间。
  • readiness probe 在关闭前尽快变为不可用。
  • 与网关、Service Mesh、注册中心的摘流时间配合。
  • 发布时观察 5xx、请求耗时和连接重置。

异步任务和线程池处理

Web Server 优雅停机不等于所有业务任务都会自动优雅结束。你还需要关注:

  • @Async 线程池。
  • 自定义 ThreadPoolExecutor。
  • 定时任务。
  • 消息消费者。
  • 批处理任务。
  • HTTP 客户端连接池。
  • 数据库连接池。

线程池建议:

@Bean
public ThreadPoolTaskExecutor applicationTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(8);
    executor.setMaxPoolSize(16);
    executor.setQueueCapacity(1000);
    executor.setThreadNamePrefix("biz-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(30);
    return executor;
}

关键点:

  • 停机时不再接收新任务。
  • 等待已提交任务执行完成。
  • 设置最大等待时间,避免无限阻塞。

消息消费者处理

如果应用消费 RocketMQ、Kafka、RabbitMQ 等消息,停机时要注意:

  • 停止拉取新消息。
  • 等待当前消息处理完成。
  • 正确提交 offset 或 ack。
  • 处理失败消息要能重试或进入死信。
  • 避免处理一半时进程被强杀。

不同消息中间件关闭方式不同,要以对应客户端文档为准。核心原则是:先停消费,再等处理完成,最后关闭客户端。

常见问题

1. 配了 graceful 但请求还是中断

检查:

  • 是否使用 kill -9。
  • 关闭超时时间是否太短。
  • 请求是否超过 timeout-per-shutdown-phase。
  • 网关或负载均衡是否提前断开连接。
  • 异步任务是否没有纳入关闭流程。

2. Kubernetes 发布时仍然有 502 / 503

检查:

  • readiness 是否及时变为不可用。
  • preStop 是否给了摘流时间。
  • terminationGracePeriodSeconds 是否足够。
  • 网关 / Ingress / Service Mesh 是否仍在转发旧连接。
  • 应用关闭时间是否超过容器宽限期。

3. Actuator shutdown 不生效

检查:

  • 是否引入 actuator。
  • management.endpoint.shutdown.enabled=true 是否配置。
  • management.endpoints.web.exposure.include 是否包含 shutdown。
  • 请求方法是否为 POST。
  • 是否被 Spring Security 拦截。

4. 停机一直卡住

检查:

  • 是否有非守护线程未退出。
  • 线程池是否未关闭。
  • 定时任务是否还在执行。
  • 消息消费者是否阻塞。
  • 数据库连接或 HTTP 调用是否长时间无超时。

生产实践清单

上线前建议确认:

  • [ ] 配置 server.shutdown=graceful。
  • [ ] 配置合理的 spring.lifecycle.timeout-per-shutdown-phase。
  • [ ] Docker / Kubernetes 停止宽限期大于应用优雅停机时间。
  • [ ] readiness 能在停机时及时拒绝流量。
  • [ ] 线程池配置了等待任务完成和最大等待时间。
  • [ ] 消息消费者支持停止拉取和处理完成后退出。
  • [ ] 不使用 kill -9 做正常发布。
  • [ ] Actuator shutdown 不暴露到公网。
  • [ ] 发布过程中监控 5xx、超时、连接重置和消息重复消费。

总结

Spring Boot 优雅停机不只是一个配置项,而是一套发布和关闭流程。

关键结论:

  1. 使用 server.shutdown=graceful 开启 Web 层优雅停机。
  2. 使用 spring.lifecycle.timeout-per-shutdown-phase 控制等待时间。
  3. 正常停止应使用 SIGTERM,不要使用 kill -9。
  4. Actuator shutdown 端点默认禁用,生产暴露要非常谨慎。
  5. Docker / Kubernetes 中要配合停止宽限期、readiness 和 preStop。
  6. 线程池、定时任务、消息消费者等业务资源需要单独设计关闭逻辑。

参考资料

  • Spring Boot 官方文档:Graceful Shutdown
  • Spring Boot 官方文档:Actuator Endpoints
  • Spring Boot 官方文档:Externalized Configuration
  • Kubernetes 官方文档:Pod Lifecycle
Java
Spring Boot Graceful Shutdown 优雅停机 Actuator Kubernetes Docker 服务治理 Java
License:  CC BY 4.0
Share

Further Reading

Sep 12, 2024

RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析

本文基于 RocketMQ 4.x 经典架构,梳理 NameServer、Broker、Producer、Consumer、Remoting 与 Store 模块,结合消息轨迹、存储模型、FastFail、事务消息和高可用部署,总结高并发场景下的实践要点。

Aug 16, 2023

Java List 核心数据结构解析:ArrayList、LinkedList 与线程安全

系统梳理 Java List 接口、ArrayList 动态数组、LinkedList 双向链表、容量扩容、遍历与 fail-fast 机制,并对比 synchronizedList、CopyOnWriteArrayList、Vector 等线程安全方案的适用场景。

Apr 24, 2023

数组基础详解:概念、存储结构与常用操作

从数组的连续存储、下标访问、Java 数组对象、一维与多维数组、遍历、查找、插入、删除、复制、排序和 Arrays 工具类出发,系统梳理数据结构学习中的数组基础。

OLDER

Docker 搭建青龙、NolanJDC 与 xdd-plus 自动化环境

NEWER

RocketMQ DLedger 高可用集群搭建与故障排查:自动选主、Docker 网络与常见踩坑

Recently Updated

  • Agent 架构设计原则:Router、Runtime 与 Business Script 的职责划分
  • RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析
  • Redis 核心概念、数据结构与高可用架构详解
  • B+树原理与 MySQL InnoDB 索引机制解析
  • MySQL AUTO_INCREMENT 插入 0 变成自增值的原因与解决方案

Trending Tags

RocketMQ Windows Feign Docker Zipkin SonarQube OkHttp HttpClient API 性能优化

Contents

©2026 Ryan's Blog. Some rights reserved. · 粤ICP备2022031588号