Spring Boot 优雅停机配置了 graceful 就够吗?10 秒窗口与任务边界
摘要:
server.shutdown=graceful只能让 Spring Boot 在关闭 Web 服务器时停止接收新请求,并等待进行中的请求完成。线程池任务、定时任务、MQ 消费、注册中心摘除和 Kubernetes 终止窗口仍需协同。本文结合 MetaLite 的公共配置与部署模板,拆解“10 秒优雅停机”真正覆盖什么,以及它为什么不是一个开关就能完成的能力。
Spring Boot 服务上线 Kubernetes 后,滚动发布偶尔出现几次 502,最常见的第一反应是加上:
server:shutdown:graceful配置没错,但如果认为它能自动处理全部关闭问题,下一次发布仍可能遇到:
- 请求已经进入旧实例,但实例被强制结束;
- Nacos 还把流量发给正在退出的节点;
- 线程池里仍有异步任务;
@Scheduled任务执行到一半;- MQ 消息已拉取但业务尚未完成;
- Kubernetes 的终止等待时间小于应用需要的时间。
优雅停机不是一个组件的功能,而是一条从流量入口到进程退出的时间链。
一、MetaLite 默认配置做了什么
backend-application.yml中包含:
server:shutdown:gracefulspring:lifecycle:timeout-per-shutdown-phase:10s两项配置分别表达:
graceful → Web 服务器停止接收新请求,并等待活动请求 timeout-per-shutdown-phase: 10s → 每个生命周期关闭阶段最多等待 10 秒这比直接结束 JVM 更安全,但 10 秒不是“系统中所有任务都保证完成”的承诺。
二、优雅停机首先要解决新流量进入问题
一个实例准备退出时,理想顺序是:
标记不再就绪 → 从负载均衡和服务发现中摘除 → 停止接收新请求 → 等待在途请求完成 → 关闭后台资源 → 结束进程顺序反过来就会出现窗口:进程已经开始关闭,网关或其他服务仍认为它健康,于是新请求继续到达。
Spring Boot 的 graceful 主要处理当前应用内的 Web 服务器,外部负载均衡、Nacos 和 Kubernetes 是否及时停止转发,还要依赖健康状态、注册中心行为和部署配置。
三、健康检查的“存活”和“就绪”不是一回事
MetaLite 提供一个返回ok的/接口,并在 Kubernetes 模板中用于 liveness 和 readiness 探针。
两类探针语义不同:
| 探针 | 问题 | 失败后的典型动作 |
|---|---|---|
| liveness | 进程是否需要重启 | 重启容器 |
| readiness | 实例是否适合接收流量 | 从 Service 端点移除 |
如果两个探针始终调用同一个、只返回ok的接口,那么应用开始退出后,它可能仍在一段时间内被认为“就绪”。
更完整的实现应让 readiness 在关闭早期先失败,而 liveness 在进程真正异常时才失败。这样流量摘除可以早于进程退出。
四、10 秒窗口应该如何确定
合理的等待时间不能拍脑袋,应观察真实请求和任务耗时:
关闭等待时间 > P99 请求耗时 + 流量摘除传播时间 + 必要的资源收尾时间如果接口超时上限本身就是 30 秒,而关闭阶段只给 10 秒,最慢的一批合法请求仍可能被截断。
反过来,把等待时间设成 10 分钟也不是免费:
- 发布速度显著下降;
- 异常任务可能长期阻止退出;
- Kubernetes 仍可能在
terminationGracePeriodSeconds到期后强杀; - 老版本实例与新版本并存时间变长。
等待时间需要同时与 HTTP 超时、RPC 超时和容器终止窗口对齐。
五、线程池任务不会因为 Web 请求结束就自动安全完成
MetaLite 有统一ThreadPoolManager,并对线程上下文、拒绝策略和未捕获异常进行治理。
但停机时仍要区分任务类型:
可丢弃任务 允许快速终止 必须完成任务 等待并设置上限 可恢复任务 保存进度后退出 只能执行一次的任务 需要幂等或外部协调如果业务方法把任务提交到线程池后立即返回,Web 请求完成不代表异步任务已经完成。线程池的 shutdown、awaitTermination 和强制中断策略需要单独定义。
六、定时任务和 MQ 消费是另一条关闭链
@Scheduled任务可能在服务退出前一秒触发。即使使用 Redis 锁保证集群只有一个实例执行,也不能保证这个实例不会在任务中途被终止。
MQ 消费同样存在:
消息已拉取 → 业务处理中 → 实例退出需要结合客户端确认机制判断消息会重投、丢失还是重复处理。因此业务仍应满足:
- 消费幂等;
- 关键步骤可重试;
- 长任务可恢复;
- 关闭时停止拉取新消息;
- 处理中的消息拥有明确等待上限。
优雅停机能减少中断,但不能替代业务幂等。
七、Kubernetes 的终止时间必须大于应用等待时间
Pod 终止时,Kubernetes 最终受terminationGracePeriodSeconds约束。应用内部准备等待 30 秒,而 Pod 只给 10 秒,后面的等待不会发生,进程会被强制结束。
部署参数至少要满足:
Kubernetes 终止窗口 > 流量摘除时间 + Spring 生命周期等待时间 + 进程退出余量还可以通过preStop先触发就绪状态变化或短暂等待,让 Endpoint 变更传播到网关和负载均衡,再进入应用关闭阶段。
八、Nacos 场景还要考虑注册中心摘除
MetaLite 内部 RPC 通过 Nacos 选择健康实例。服务关闭期间需要确认:
- 实例何时从 Nacos 注销;
- 消费者多久能感知实例列表变化;
- 查询到实例后、真正发请求前实例是否可能下线;
- 调用失败后是否允许对幂等请求重试;
- 全实例广播是否会命中正在退出的节点。
服务发现永远存在状态传播延迟。因此即使摘除逻辑正确,客户端仍要把连接拒绝、连接重置视为正常分布式故障,而不是假设实例列表绝对实时。
九、一次可验证的停机演练应该看什么
不要只检查日志中是否出现“优雅停机”。更有效的演练是:
- 持续发送短请求和长请求;
- 同时触发滚动发布;
- 观察 readiness 何时失败;
- 观察 Nacos 实例何时消失;
- 检查是否出现 502、连接重置和超时;
- 检查线程池、定时任务和 MQ 消费是否中断;
- 检查容器是正常退出还是被强杀;
- 核对新旧版本并存期间的数据兼容性。
只有经过滚动发布压测,才能知道 10 秒到底够不够。
十、graceful是起点,不是完成标志
MetaLite 将优雅停机作为公共默认值,是为了让每个应用至少从安全方向开始。但完整闭环还包括:
Spring Boot 生命周期 + Web 服务器 + 线程池 + 定时任务 + MQ 消费者 + Nacos 服务发现 + Kubernetes 探针与终止窗口 + 业务幂等真正的优雅停机,不是“进程多等了 10 秒”,而是在这段时间里停止新增工作、完成必要工作,并把无法完成的工作变成可恢复状态。
框架简介
MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。
源码基线
JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目backend-bom为准。
作者简介
15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。
持续更新
MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。
在线演示
演示地址: https://admin.metalite.top/
演示账号: guess
演示密码: admin@2026