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 的优雅关机功能,或者配置了线程池的优雅停止,就能实现优雅启停。但实际上,优雅启停是一个综合性的概念,涉及多个方面的配合。

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

总结

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

相关推荐
天草二十六_简村人17 分钟前
kong搭建一套微信小程序的公司研发环境
java·后端·微信小程序·小程序·kong
小安同学iter2 小时前
SpringMVC(五)拦截器
java·开发语言·spring boot·spring·java-ee
鱼樱前端2 小时前
前端程序员集体破防!AI工具same.dev像素级抄袭你的代码,你还能高傲多久?
前端·javascript·后端
梁萌2 小时前
搭建Spring Boot Admin监控系统
spring boot·监控·admin·springbootadmin
羊思茗5202 小时前
Spring Boot中@Valid 与 @Validated 注解的详解
java·spring boot·后端
尤宸翎2 小时前
Julia语言的饼图
开发语言·后端·golang
穆韵澜3 小时前
SQL语言的云计算
开发语言·后端·golang
uhakadotcom3 小时前
提升PyODPS性能的实用技巧
后端·面试·github
字节源流3 小时前
【SpringMVC】入门版
java·后端
Code哈哈笑3 小时前
【JavaEE】创建SpringBoot第一个项目,Spring Web MVC⼊⻔,从概念到实战的 Web 开发进阶之旅
spring boot·spring·java-ee