一、什么是优雅停机

想象你正在看一部电影,突然有人直接拔掉电源,画面瞬间消失——这就是暴力停机的后果。而优雅停机更像是按下遥控器的暂停键,让系统有机会保存进度、关闭连接,最后安全退出。

在Spring Boot应用中,优雅停机指的是:当需要关闭服务时,先拒绝新请求,处理完存量任务,释放数据库连接等资源,最后终止进程。这样能避免数据丢失或事务中断,就像餐厅打烊前会让现有顾客吃完,但不再接待新客人。

二、为什么需要优雅停机

线上服务重启或更新时,直接kill -9可能导致:

  1. 数据库事务中途断开,留下"半成品"数据
  2. 消息队列消费到一半,消息既没成功也没回滚
  3. 用户突然收到错误提示,体验断崖式下跌

典型场景举例

  • 支付系统正在处理转账,突然停机可能导致A账户已扣款,B账户未到账
  • 电商订单提交到一半,库存已经扣除但订单记录丢失

三、Spring Boot实现方案详解

技术栈:Spring Boot 2.7 + Tomcat + JDBC

方案1:使用Actuator端点(适合简单应用)

// 启用shutdown端点(application.yml)
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

// 调用方式:POST /actuator/shutdown
// 注意:需要安全认证,建议配合Spring Security使用

缺点:无法自定义停机逻辑,比如等待任务队列清空。

方案2:自定义停机钩子(推荐)

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApp.class);
        
        // 注册停机钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("收到停机信号,开始清理...");
            
            // 示例:关闭数据源连接池
            HikariDataSource dataSource = app.run(args).getBean(HikariDataSource.class);
            dataSource.close(); // 优雅关闭连接池
            
            // 这里可以添加其他清理逻辑,比如:
            // 1. 停止接收新MQ消息
            // 2. 等待正在处理的线程完成
            // 3. 持久化缓存数据
        }));
        
        app.run(args);
    }
}

方案3:结合Spring事件机制(更精细控制)

@Component
public class GracefulShutdown {
    
    @Autowired 
    private DataSource dataSource;
    
    @EventListener(ContextClosedEvent.class) // Spring容器关闭事件
    public void onShutdown() throws InterruptedException {
        // 第一步:标记服务不可用
        HealthIndicator healthIndicator = () -> Health.down().build();
        
        // 第二步:等待30秒让现有请求完成
        Thread.sleep(30000); 
        
        // 第三步:关闭数据库连接池
        if(dataSource instanceof HikariDataSource) {
            ((HikariDataSource)dataSource).close();
        }
    }
}

四、关键注意事项

  1. 超时时间设置
    等待处理中的请求时,必须设置合理超时(如30秒),避免无限等待。可以在配置中添加:

    server:
      shutdown: graceful
    spring:
      lifecycle:
        timeout-per-shutdown-phase: 30s
    
  2. 分布式系统协调
    如果是微服务架构,还需要:

    • 从注册中心(如Nacos)主动下线服务
    • 通过API网关切断流量
  3. 线程池处理
    自定义线程池需要额外关闭逻辑:

    @Bean(destroyMethod = "shutdown")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setAwaitTerminationSeconds(60); // 等待任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
    
  4. 测试验证方法

    • 发送请求后立即执行kill -15 [PID](SIGTERM信号)
    • 观察日志是否显示清理流程
    • 检查数据库事务是否完整提交

五、不同场景下的选择建议

场景 推荐方案 理由
单体应用简单需求 Actuator端点 无需编码,快速实现
需要自定义清理逻辑 停机钩子+Spring事件 灵活控制各模块关闭顺序
使用Kubernetes 配置preStop Hook 与容器编排系统深度集成

六、总结

优雅停机就像"安全着陆",核心思路是:

  1. 信号通知:通过SIGTERM或API触发流程
  2. 流量切断:拒绝新请求但处理存量
  3. 资源清理:按顺序关闭数据库、MQ等连接
  4. 最终退出:确认无误后终止进程

完整的实现示例可在GitHub找到(模拟项目地址)。实际应用中,建议结合Prometheus监控确保每次停机都符合预期耗时。