
1. 为什么需要优雅停机机制在传统的SpringBoot应用停机过程中直接kill进程会导致正在处理的请求被强制中断可能引发数据不一致、事务未提交、资源未释放等问题。想象一下这样的场景一个支付请求正在处理中突然服务被强制终止这笔交易可能既没有成功也没有失败记录给后续对账带来巨大麻烦。优雅停机Graceful Shutdown的核心目标就是让应用在收到停止信号后能够拒绝新请求进入等待已接收请求处理完成释放占用的资源最后才真正终止进程2. SpringBoot优雅停机实现方案2.1 基于内嵌容器的方案SpringBoot 2.3版本原生支持优雅停机通过简单的配置即可启用# application.properties server.shutdowngraceful spring.lifecycle.timeout-per-shutdown-phase30s这个配置会让SpringBoot在收到SIGTERM信号时停止接收新请求等待30秒让现有请求完成如果超时仍有请求未完成则强制终止注意这个超时时间需要根据业务平均处理时间合理设置太长影响发布效率太短可能无法完成处理。2.2 自定义Endpoint方案对于更精细的控制可以自定义管理端点RestController Endpoint(id graceful-shutdown) public class GracefulShutdownEndpoint { Autowired private ConfigurableApplicationContext context; WriteOperation public String gracefulShutdown(Nullable Integer timeout) { new Thread(() - { try { Thread.sleep(timeout ! null ? timeout * 1000 : 30000); context.close(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); return Shutdown initiated; } }这样可以通过POST /actuator/graceful-shutdown?timeout60来触发停机并指定超时时间。3. 生产环境最佳实践3.1 Kubernetes环境适配在K8s中部署时需要配置正确的生命周期钩子apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: app lifecycle: preStop: exec: command: [sh, -c, curl -X POST http://localhost:8080/actuator/graceful-shutdown?timeout30] terminationGracePeriodSeconds: 60关键配置点preStop钩子触发优雅停机terminationGracePeriodSeconds要比优雅停机超时时间长3.2 常见问题排查问题1停机时间过长检查是否有长时间运行的异步任务使用jstack分析线程状态考虑拆分耗时任务为独立服务问题2连接泄漏确保所有Connection都有try-with-resources添加连接池监控Bean public DataSource dataSource() { HikariDataSource ds new HikariDataSource(); ds.setMetricRegistry(new MetricRegistry()); return ds; }4. 高级场景处理4.1 分布式事务场景对于Seata等分布式事务框架需要额外处理Component public class GracefulShutdownListener implements ApplicationListenerContextClosedEvent { Override public void onApplicationEvent(ContextClosedEvent event) { GlobalTransactionContext.reload(); // 等待分布式事务完成 while(hasOngoingGlobalTransaction()) { Thread.sleep(1000); } } }4.2 消息队列消费者对于Kafka消费者需要正确处理偏移量KafkaListener(topics order-topic) public void listen(String message) { try { processOrder(message); } catch (Exception e) { // 确保异常时不会提交偏移量 throw e; } } PreDestroy public void destroy() { // 确保所有消息处理完成再关闭 kafkaListenerEndpointRegistry.stop(); }5. 监控与指标建议添加优雅停机相关监控指标Bean public MeterRegistryCustomizerMeterRegistry metrics() { return registry - { Gauge.builder(app.shutdown.remaining, () - shutdownHandler.getRemainingRequests()) .register(registry); }; }在Grafana中可以设置告警规则当remaining_requests 0持续时间超过阈值时告警监控平均停机时间变化趋势6. 实战经验分享在实际项目中我们发现几个关键点负载均衡器粘滞时间确保LB的deregistration delay大于优雅停机超时时间否则客户端可能收到503错误。健康检查配置在preStop阶段应将健康检查状态置为OUT_OF_SERVICEPostConstruct public void init() { HealthIndicatorRegistry registry ... registry.register(readiness, () - Health.down().build()); }线程池处理对于自定义线程池需要重写shutdown逻辑Bean(destroyMethod shutdownGracefully) public ExecutorService asyncExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(30); return executor.getThreadPoolExecutor(); }测试验证建议使用自动化测试验证优雅停机Test public void testGracefulShutdown() throws Exception { // 发送测试请求 CompletableFutureString future asyncSendRequest(); // 触发停机 triggerShutdown(); // 验证请求完成 assertThat(future.get(10, SECONDS)).isNotNull(); }