☰
SpringBoot优雅停机,别再直接kill-9了
2026/9/28 4:06:28 网站建设 项目流程

生产环境发布新版本时,一条kill -9下去,正在处理的订单请求瞬间中断,客户端收到 502,数据库里留下半截事务。这个场景在微服务架构中并不罕见,而问题的根源往往不在业务代码,而在停机方式。

kill -9 到底做了什么

kill -9发送的是SIGKILL信号,操作系统会直接终止进程,JVM 没有机会执行任何清理逻辑。Spring 的ShutdownHook不会被触发,@PreDestroy方法不会执行,Tomcat 不会等待正在处理的请求完成,线程池中的任务直接消失。换句话说,kill -9 不是“关闭服务”,而是“砸掉服务”。

正确的做法是使用kill -TERM(即kill -2或kill <pid>),它会触发 JVM 的 ShutdownHook,让 Spring 有机会完成应用上下文的正常关闭。

Spring Boot 内置的优雅停机

从 Spring Boot 2.3 开始,框架内置了优雅停机支持,覆盖 Tomcat、Jetty、Reactor Netty 和 Undertow 四种内嵌服务器。Spring Boot 3.4 之后,优雅停机已默认启用,但显式配置仍然是推荐做法。

yaml
复制
下载
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s

配置生效后,收到SIGTERM时的行为会发生变化:Web 服务器在网络层停止接收新请求,同时给正在处理的请求一段宽限期完成。Tomcat、Jetty 和 Reactor Netty 的做法是直接不再 accept 新连接;Undertow 则会返回 503 状态码。

宽限期默认为 30 秒,通过timeout-per-shutdown-phase调整。这个值需要结合接口的最长执行时间设定:如果某个接口正常需要 60 秒,而宽限期只有 20 秒,停机时这个请求仍会被强制中断。

但只配一个 YAML 远远不够

优雅停机失效的最常见原因,不是 Spring 配置写错了,而是平台层与应用层的时间预算没有对齐。

Docker 的docker stop默认在 10 秒后发送SIGKILL强制终止容器。如果你的 Spring Boot 配置了 30 秒的宽限期,实际只有 10 秒可用。Kubernetes 的默认terminationGracePeriodSeconds是 30 秒,看似够用,但需要注意这个 30 秒是从发送 SIGTERM 到 SIGKILL 的总预算,包含了流量摘除、preStop 钩子、日志刷新等所有环节。

推荐的对齐方式是让平台层的终止预算略大于应用层的停机预算。例如应用配置timeout-per-shutdown-phase: 20s,Kubernetes 设置terminationGracePeriodSeconds: 40,并加一个短暂的preStop延迟让就绪探针先失效、流量从端点摘除:

yaml
复制
下载
lifecycle: preStop: exec: command: ["sh", "-c", "sleep 5"] terminationGracePeriodSeconds: 40

这 5 秒的 preStop 睡眠给服务发现和负载均衡器留出传播时间,避免停机信号发出后仍有新请求被路由到即将关闭的实例上。

后台任务:优雅停机最容易漏掉的部分

Web 服务器停止接收请求,不等于所有线程都安全了。自建的ThreadPoolTaskExecutor、Kafka 消费者、定时调度任务,不会自动因为 WebServer 停止而安全结束。

对于线程池,需要设置setWaitForTasksToCompleteOnShutdown(true)和setAwaitTerminationSeconds(60),确保停机时等待任务完成而非直接丢弃。对于消息消费者,正确的停止顺序是:先停止拉取新消息,再等待正在处理的消息完成,最后关闭数据库连接和下游客户端。顺序反了,消费者可能在连接池已关闭后继续取消息,导致提交失败。

可以用@PreDestroy或实现DisposableBean来编排这些清理逻辑。@PreDestroy的执行时机早于DisposableBean,适合处理依赖较少的清理。如果多个组件存在启停依赖关系,SmartLifecycle的phase值可以控制顺序,停机时按 phase 反向执行。

验证:别在 IDE 里点红色按钮

IDE 的停止按钮很可能直接终止进程而不发送正确的SIGTERM信号,在本地看不到优雅停机的任何行为。验证必须在容器或真实启动环境下进行:docker stop或删除 Pod,同时观察日志中是否出现Commencing graceful shutdown和Graceful shutdown complete。

一个实用的测试方式是启动一个故意延迟数秒的接口,在请求进行中发送SIGTERM。预期结果是:进行中的请求在宽限期内正常返回,新请求由其他实例接管,进程在终止预算内退出。如果日志显示请求被中断或出现 502,说明时间线还没有对齐。

优雅停机从来不是一个server.shutdown=graceful就能解决的事情。Spring 负责 WebServer 的基础等待能力,平台负责信号与终止预算,应用本身负责后台任务和资源的关闭顺序。三者对齐,滚动发布才不会把正常请求当作可丢弃的尾巴。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询