Spring Boot 优雅停机实践:Graceful Shutdown、Actuator 与 Kubernetes 发布场景
优雅停机(Graceful Shutdown)指应用收到停止信号后,不是立即中断进程,而是先停止接收新请求,等待已经进入应用的请求、任务或资源释放完成,再关闭进程。
在微服务发布、容器重启、Kubernetes 滚动更新、扩缩容和故障迁移场景中,优雅停机非常重要。否则应用可能出现:
- 请求处理中断。
- 事务执行一半失败。
- 消息消费重复或丢失。
- 线程池任务被强制终止。
- 上游仍然把流量打到即将关闭的实例。
这篇文章整理 Spring Boot 优雅停机的配置方式、关闭流程、Actuator shutdown 端点、Docker / Kubernetes 场景,以及线程池、定时任务、消息消费者等业务资源的处理建议。
什么是优雅停机
一个理想的关闭流程通常是:
- 应用收到停止信号,例如
SIGTERM。 - 应用从注册中心或负载均衡中摘除,或者 readiness 变为不可用。
- 停止接收新请求。
- 等待正在处理的请求完成。
- 停止异步任务、定时任务和消息消费者。
- 关闭数据库连接池、HTTP 客户端、线程池等资源。
- 应用进程退出。
优雅停机的核心不是“永远等下去”,而是在一个可控的时间窗口内尽量完成已有工作,超时后仍要退出。
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
验证方法:
- 准备一个执行时间较长的接口,例如 sleep 10 秒。
- 请求进入后立即发送
SIGTERM。 - 观察请求是否能正常返回。
- 再发起新请求,观察是否被拒绝或无法建立连接。
- 查看应用是否在超时时间内退出。
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 时,通常会:
- 将 Pod 标记为 Terminating。
- 执行
preStophook。 - 发送
SIGTERM给容器主进程。 - 等待
terminationGracePeriodSeconds。 - 超时后发送
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 优雅停机不只是一个配置项,而是一套发布和关闭流程。
关键结论:
- 使用
server.shutdown=graceful开启 Web 层优雅停机。 - 使用
spring.lifecycle.timeout-per-shutdown-phase控制等待时间。 - 正常停止应使用
SIGTERM,不要使用kill -9。 - Actuator shutdown 端点默认禁用,生产暴露要非常谨慎。
- Docker / Kubernetes 中要配合停止宽限期、readiness 和 preStop。
- 线程池、定时任务、消息消费者等业务资源需要单独设计关闭逻辑。