Spring Boot 的优雅启停:确保停机不影响交易

Spring Boot 的优雅启停:确保停机不影响交易

在实际生产环境中,项目上线、发版或版本升级时,服务直接关停可能导致正在执行的交易失败,尤其是在分布式事务和异步调用场景下。许多人对"优雅启停"的理解不够深入,导致问题频发。本文将详细介绍如何实现 Spring Boot 的优雅启停,确保停机时已发起的请求能够顺利完成,同时新请求自动切换到其他可用节点,从而保证业务连续性。

背景

微服务架构中,分布式事务与异步调用非常普遍。例如,审批流程完成后异步调用支付模块扣款(失败会重试)。如果服务急停,可能导致支付模块因服务关闭、线程中断或队列积压而扣款失败。如果缺乏有效的异常处理策略,用户体验会受到严重影响。

问题分析

本质上,微服务架构下的分布式事务容易因远程调用失败而受影响。这种情况只能采用相关策略保证最终一致性。

有观点认为 MQ 能够解决该问题,但实际上,单纯 MQ 解决不了分布式事务问题,还是要解决幂等、超时失效等问题以及 MQ 的消息必达等问题。必须配合优雅启停、幂等设计、补偿机制等组合策略才能实现可靠交易。

当前方案

目前,我们的方案是在发版时先将流量引导至其他可用中心,但这一流程涉及流量控制、会话保持等问题,容易引入新的风险。本文旨在介绍如何实现"优雅启停",确保停机时已发起请求能顺利完成,新请求切换到其他可用节点。

1. 解决方案概述

  1. 多中心并行发版:各中心并行升级,确保始终有可用实例。
  2. 服务下线前的流量切换:使用服务注册中心(如 Eureka)将待下线实例置为"DOWN"。
  3. 进程关闭信号选择:使用 kill -15 (SIGTERM)触发优雅关闭,而非 kill -9(SIGKILL)。
  4. 线程池的优雅停止:配置线程池等待任务完成,避免强制中断。
  5. Spring Boot 2.3 内置优雅关机:启用 server.shutdown=graceful,设置超时时间。
  6. 启动时的优雅准备:检测依赖、初始化资源,确保服务就绪后再接受流量。

2. 多中心并行发版

  • 方案说明:多中心架构下,各中心并行发版,中心内顺序升级,确保任何时刻每个中心至少一台机器运行,避免单中心全部下线。

3. 服务下线前的流量切换

  • 实现方式:发版前,通过服务注册中心(如 Eureka)将待下线实例状态置为"DOWN",使流量自动切换。例如:

    bash 复制代码
    curl -X POST "http://localhost:8080/actuator/service-registry?status=DOWN" -H "Content-Type: application/json"
    • 检测方法:
      • 请求 Eureka API,检查待下线实例是否已从注册列表中移除。
      • 通过负载均衡器或网关的监控界面,确认流量已不再路由到该实例。
  • 注意事项:

    • 此方法模拟流量切换,先下线再等待交易完成。
    • Actuator 接口存在安全风险,生产环境应限制访问或配置认证。
  • 优化:确保线程池中任务执行完成后再做启停。增加对线程池资源的监控(检查方法同下边启动时检查)

    • 手动将 ThreadPoolExecutor 注册到 Micrometer 以便在 /actuator/metrics 获取线程池信息
    java 复制代码
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolTaskExecutor executor, MeterRegistry registry) {
        ThreadPoolExecutor threadPoolExecutor = executor.getThreadPoolExecutor();
    
        registry.gauge("executor.pool.size", threadPoolExecutor, ThreadPoolExecutor::getPoolSize);
        registry.gauge("executor.active.count", threadPoolExecutor, ThreadPoolExecutor::getActiveCount);
        registry.gauge("executor.queue.size", threadPoolExecutor, e -> e.getQueue().size());
        registry.gauge("executor.completed.tasks", threadPoolExecutor, ThreadPoolExecutor::getCompletedTaskCount);
    
        return threadPoolExecutor;
    }
    • 如果你使用的是 Spring Boot 3.x,Micrometer 3 以及 Actuator 自带 ExecutorService 监控功能,你可以直接用 ExecutorServiceMetrics 自动注册线程池指标:

      java 复制代码
      @Bean
      public ExecutorService monitoredExecutor(ThreadPoolTaskExecutor executor, io.micrometer.core.instrument.MeterRegistry registry) {
          ThreadPoolExecutor threadPoolExecutor = executor.getThreadPoolExecutor();
          return ExecutorServiceMetrics.monitor(registry, threadPoolExecutor, "custom-executor", "task");
      }

4. 进程关闭信号的选择

  • 推荐做法:使用 kill -15(SIGTERM)通知应用平滑关闭,而非 kill -9(SIGKILL)。
  • 优势:SIGTERM 触发 Spring Boot 关闭钩子,执行资源释放、线程池关闭、完成未结束请求等清理工作,降低任务中断风险。

5. 线程池的优雅停止

  • ThreadPoolTaskExecutor:配置 waitForTaskToCompleteOnShutdown=true,设置 awaitTerminationSeconds,确保等待任务完成。
  • ExecutorService:使用 shutdown() 发起平滑关闭,调用 awaitTermination() 等待任务结束,而非 shutdownNow()
  • 非 Spring 管理的 Bean:监听 ApplicationListener 事件,在 ContextClosedEvent 中统一处理资源关闭。

6. Spring Boot 2.3 内置优雅关机

  • 配置:

    properties 复制代码
    server.shutdown=graceful
    spring.lifecycle.timeout-per-shutdown-phase=30s
  • 作用:嵌入式服务器停止接收新请求,允许已建立连接完成处理,降低服务中断风险。

7. 启动时的优雅准备

  • 检测方法:

    • 请求应用的 /actuator/health 或自定义健康检查端点,确认所有依赖(数据库、缓存、远程服务)均正常。
    • 请求应用的 /actuator/info 或自定义信息端点,确认所有必要的初始化操作已完成。
    • 请求 Eureka API,检查自身实例是否已成功注册,且状态为 UP。
  • 确保:

    • 各项依赖已正常建立连接。
    • 所有初始化操作已完成。
    • 注册成功后才处理外部请求,避免流量打到未就绪实例。

优雅启停理解的澄清

很多人认为,只要使用了 Spring Boot 2.3 的优雅关机功能,或者配置了线程池的优雅停止,就能实现优雅启停。但实际上,优雅启停是一个综合性的概念,涉及多个方面的配合。

  • 流量切换:即使应用内部能优雅停止,如果流量没有切换,新的请求仍然会打到即将下线的实例上,导致失败。
  • 依赖项的关闭: 优雅的关闭,需要保证依赖项的正常关闭,例如数据库连接,消息队列连接等。
  • 启动时的就绪状态:同样,启动时也需要确保所有依赖项都已就绪,才能开始处理请求。

总结

优雅启停是系统稳定运行的关键。通过多中心并行发版、流量切换、优雅关闭信号、线程池管理、内置支持和启动准备等措施,可有效降低服务中断风险,确保停机不影响交易。

相关推荐
调试人生的显微镜几秒前
深入剖析 iOS 26 系统流畅度,多工具协同监控与性能优化实践
后端
蹦跑的蜗牛1 分钟前
Spring Boot使用Redis实现消息队列
spring boot·redis·后端
非凡ghost10 分钟前
HWiNFO(专业系统信息检测工具)
前端·javascript·后端
非凡ghost12 分钟前
FireAlpaca(免费数字绘图软件)
前端·javascript·后端
非凡ghost19 分钟前
Sucrose Wallpaper Engine(动态壁纸管理工具)
前端·javascript·后端
间彧22 分钟前
从零到一搭建Spring Cloud Alibbaba项目
后端
楼田莉子23 分钟前
C++学习:C++11关于类型的处理
开发语言·c++·后端·学习
LSTM9727 分钟前
使用 Java 对 PDF 添加水印:提升文档安全与版权保护
后端
该用户已不存在27 分钟前
Gemini CLI 扩展,把Nano Banana 搬到终端
前端·后端·ai编程
用户2986985301430 分钟前
Spire.Doc 实践指南:将Word 文档转换为 XML
后端·.net