SpringBoot中3种优雅停机的实现方式

06-01 1156阅读

引言

应用的启停是一个常见操作。然而,突然终止一个正在运行的应用可能导致正在处理的请求失败、数据不一致等问题。优雅停机(Graceful Shutdown)是指应用在接收到停止信号后,能够妥善处理现有请求、释放资源,然后再退出的过程。本文将详细介绍SpringBoot中实现优雅停机的三种方式。

什么是优雅停机?

优雅停机指的是应用程序在收到停止信号后,不是立即终止,而是遵循以下步骤有序退出:

  1. 停止接收新的请求
  2. 等待正在处理的请求完成
  3. 关闭各种资源连接(数据库连接池、线程池、消息队列连接等)
  4. 完成必要的清理工作
  5. 最后退出应用

优雅停机的核心价值在于:

  • 提高用户体验,避免请求突然中断
  • 保障数据一致性,防止数据丢失

    方式一:SpringBoot内置的优雅停机支持

    原理与支持版本

    从Spring Boot 2.3版本开始,框架原生支持优雅停机机制。这是最简单且官方推荐的实现方式。

    当应用接收到停止信号(如SIGTERM)时,内嵌的Web服务器(如Tomcat、Jetty或Undertow)会执行以下步骤:

    1. 停止接收新的连接请求
    2. 设置现有连接的keepalive为false
    3. 等待所有活跃请求处理完成或超时
    4. 关闭应用上下文和相关资源

    配置方法

    在application.properties或application.yml中添加简单配置即可启用:

    server:
      shutdown: graceful
    spring:
      lifecycle:
        timeout-per-shutdown-phase: 30s
    

    这里的timeout-per-shutdown-phase指定了等待活跃请求完成的最大时间,默认为30秒。

    实现示例

    下面是一个完整的SpringBoot应用示例,启用了优雅停机:

    @SpringBootApplication
    public class GracefulShutdownApplication {
        private static final Logger logger = LoggerFactory.getLogger(GracefulShutdownApplication.class);
        public static void main(String[] args) {
            SpringApplication.run(GracefulShutdownApplication.class, args);
            logger.info("Application started");
        }
        
        @RestController
        @RequestMapping("/api")
        static class SampleController {
            
            @GetMapping("/quick")
            public String quickRequest() {
                return "Quick response";
            }
            
            @GetMapping("/slow")
            public String slowRequest() throws InterruptedException {
                // 模拟长时间处理的请求
                logger.info("Start processing slow request");
                Thread.sleep(10000); // 10秒
                logger.info("Finished processing slow request");
                return "Slow response completed";
            }
        }
        
        @Bean
        public ApplicationListener contextClosedEventListener() {
            return event -> logger.info("Received spring context closed event - shutting down");
        }
    }
    

    测试验证

    1. 启动应用
    2. 发起一个长时间运行的请求:curl http://localhost:8080/api/slow
    3. 在处理过程中,向应用发送SIGTERM信号:kill -15 ,如果是IDEA开发环境,可以点击一次停止服务按钮
    4. 观察日志输出:应该能看到应用等待长请求处理完成后才关闭

    优缺点

    优点:

    • 配置简单,官方原生支持
    • 无需额外代码,维护成本低
    • 适用于大多数Web应用场景
    • 与Spring生命周期事件完美集成

      缺点:

      • 仅支持Spring Boot 2.3+版本
      • 对于超出HTTP请求的场景(如长时间运行的作业)需要额外处理
      • 灵活性相对较低,无法精细控制停机流程
      • 只能设置统一的超时时间

        适用场景

        • Spring Boot 2.3+版本的Web应用
        • 请求处理时间可预期,不会有超长时间运行的请求
        • 微服务架构中的标准服务

          方式二:使用Actuator端点实现优雅停机

          原理与实现

          Spring Boot Actuator提供了丰富的运维端点,其中包括shutdown端点,可用于触发应用的优雅停机。这种方式的独特之处在于它允许通过HTTP请求触发停机过程,适合需要远程操作的场景。

          配置步骤

          1. 添加Actuator依赖:
              org.springframework.boot
              spring-boot-starter-actuator
          
          
          1. 启用并暴露shutdown端点:
          management:
            endpoint:
              shutdown:
                enabled: true
            endpoints:
              web:
                exposure:
                  include: shutdown
                base-path: /management
            server:
              port: 8081  # 可选:为管理端点设置单独端口
          

          使用方法

          通过HTTP POST请求触发停机:

          curl -X POST http://localhost:8081/management/shutdown
          

          请求成功后,会返回类似如下响应:

          {
            "message": "Shutting down, bye..."
          }
          

          安全性考虑

          由于shutdown是一个敏感操作,必须考虑安全性:

          spring:
            security:
              user:
                name: admin
                password: secure_password
                roles: ACTUATOR
          management:
            endpoints:
              web:
                exposure:
                  include: shutdown
            endpoint:
              shutdown:
                enabled: true
          # 配置端点安全
          management.endpoints.web.base-path: /management
          

          使用安全配置后的访问方式:

          SpringBoot中3种优雅停机的实现方式
          (图片来源网络,侵删)
          curl -X POST http://admin:secure_password@localhost:8080/management/shutdown
          

          完整实现示例

          @SpringBootApplication
          @EnableWebSecurity
          public class ActuatorShutdownApplication {
              private static final Logger logger = LoggerFactory.getLogger(ActuatorShutdownApplication.class);
              public static void main(String[] args) {
                  SpringApplication.run(ActuatorShutdownApplication.class, args);
                  logger.info("Application started with Actuator shutdown enabled");
              }
              
              @Bean
              public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
                  http.csrf().disable()
                      .authorizeHttpRequests()
                      .requestMatchers("/management/**").hasRole("ACTUATOR")
                      .anyRequest().permitAll()
                      .and()
                      .httpBasic();
                  
                  return http.build();
              }
              
              @RestController
              static class ApiController {
                  
                  @GetMapping("/api/hello")
                  public String hello() {
                      return "Hello, world!";
                  }
              }
              
              @Bean
              public ApplicationListener shutdownListener() {
                  return event -> {
                      logger.info("Received shutdown signal via Actuator");
                      
                      // 等待活跃请求完成
                      logger.info("Waiting for active requests to complete...");
                      try {
                          Thread.sleep(5000); // 简化示例,实际应监控活跃请求
                      } catch (InterruptedException e) {
                          Thread.currentThread().interrupt();
                      }
                      
                      logger.info("All requests completed, shutting down");
                  };
              }
          }
          

          优缺点

          优点:

          • 可以通过HTTP请求远程触发停机
          • 适合管理工具和运维脚本集成
          • 可以与Spring Security集成实现访问控制
          • 支持所有Spring Boot版本(包括2.3之前的版本)

            缺点:

            SpringBoot中3种优雅停机的实现方式
            (图片来源网络,侵删)
            • 需要额外配置和依赖
            • 潜在的安全风险,需谨慎保护端点
            • 对于内部复杂资源的关闭需要额外编码

              适用场景

              • 需要通过HTTP请求触发停机的场景
              • 使用运维自动化工具管理应用的部署
              • 集群环境中需要按特定顺序停止服务
              • 内部管理系统需要直接控制应用生命周期

                方式三:自定义ShutdownHook实现优雅停机

                原理与实现

                JVM提供了ShutdownHook机制,允许在JVM关闭前执行自定义逻辑。通过注册自定义的ShutdownHook,我们可以实现更加精细和灵活的优雅停机控制。这种方式的优势在于可以精确控制资源释放顺序,适合有复杂资源管理需求的应用。

                基本实现步骤

                1. 创建自定义的ShutdownHandler类
                2. 注册JVM ShutdownHook
                3. 在Hook中实现自定义的优雅停机逻辑

                完整实现示例

                以下是一个包含详细注释的完整示例:

                SpringBoot中3种优雅停机的实现方式
                (图片来源网络,侵删)
                @SpringBootApplication
                public class CustomShutdownHookApplication {
                    private static final Logger logger = LoggerFactory.getLogger(CustomShutdownHookApplication.class);
                    public static void main(String[] args) {
                        ConfigurableApplicationContext context = SpringApplication.run(CustomShutdownHookApplication.class, args);
                        
                        // 注册自定义ShutdownHook
                        registerShutdownHook(context);
                        
                        logger.info("Application started with custom shutdown hook");
                    }
                    
                    private static void registerShutdownHook(ConfigurableApplicationContext context) {
                        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                            logger.info("Executing custom shutdown hook");
                            try {
                                // 1. 停止接收新请求(如果是Web应用)
                                if (context.containsBean("tomcatServletWebServerFactory")) {
                                    TomcatServletWebServerFactory server = context.getBean(TomcatServletWebServerFactory.class);
                                    logger.info("Stopping web server to reject new requests");
                                    // 注意: 实际应用中需要找到正确方式停止特定Web服务器
                                }
                                
                                // 2. 等待活跃请求处理完成
                                logger.info("Waiting for active requests to complete");
                                // 这里可以添加自定义等待逻辑,如检查活跃连接数或线程状态
                                Thread.sleep(5000); // 简化示例
                                
                                // 3. 关闭自定义线程池
                                shutdownThreadPools(context);
                                
                                // 4. 关闭消息队列连接
                                closeMessageQueueConnections(context);
                                
                                // 5. 关闭数据库连接池
                                closeDataSourceConnections(context);
                                
                                // 6. 执行其他自定义清理逻辑
                                performCustomCleanup(context);
                                
                                // 7. 最后关闭Spring上下文
                                logger.info("Closing Spring application context");
                                context.close();
                                
                                logger.info("Graceful shutdown completed");
                            } catch (Exception e) {
                                logger.error("Error during graceful shutdown", e);
                            }
                        }, "GracefulShutdownHook"));
                    }
                    
                    private static void shutdownThreadPools(ApplicationContext context) {
                        logger.info("Shutting down thread pools");
                        
                        // 获取所有ExecutorService类型的Bean
                        Map executors = context.getBeansOfType(ExecutorService.class);
                        
                        executors.forEach((name, executor) -> {
                            logger.info("Shutting down executor: {}", name);
                            executor.shutdown();
                            try {
                                // 等待任务完成
                                if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
                                    logger.warn("Executor did not terminate in time, forcing shutdown: {}", name);
                                    executor.shutdownNow();
                                }
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                                logger.warn("Interrupted while waiting for executor shutdown: {}", name);
                                executor.shutdownNow();
                            }
                        });
                    }
                    
                    private static void closeMessageQueueConnections(ApplicationContext context) {
                        logger.info("Closing message queue connections");
                        
                        // 示例:关闭RabbitMQ连接
                        if (context.containsBean("rabbitConnectionFactory")) {
                            try {
                                ConnectionFactory rabbitFactory = context.getBean(ConnectionFactory.class);
                                // 适当地关闭连接
                                logger.info("Closed RabbitMQ connections");
                            } catch (Exception e) {
                                logger.error("Error closing RabbitMQ connections", e);
                            }
                        }
                        
                        // 示例:关闭Kafka连接
                        if (context.containsBean("kafkaConsumerFactory")) {
                            try {
                                // 关闭Kafka连接的代码
                                logger.info("Closed Kafka connections");
                            } catch (Exception e) {
                                logger.error("Error closing Kafka connections", e);
                            }
                        }
                    }
                    
                    private static void closeDataSourceConnections(ApplicationContext context) {
                        logger.info("Closing datasource connections");
                        
                        // 获取所有DataSource类型的Bean
                        Map dataSources = context.getBeansOfType(DataSource.class);
                        
                        dataSources.forEach((name, dataSource) -> {
                            try {
                                // 对于HikariCP连接池
                                if (dataSource instanceof HikariDataSource) {
                                    ((HikariDataSource) dataSource).close();
                                    logger.info("Closed HikariCP datasource: {}", name);
                                }
                                // 可以添加其他类型连接池的关闭逻辑
                                else {
                                    // 尝试通过反射调用close方法
                                    Method closeMethod = dataSource.getClass().getMethod("close");
                                    closeMethod.invoke(dataSource);
                                    logger.info("Closed datasource: {}", name);
                                }
                            } catch (Exception e) {
                                logger.error("Error closing datasource: {}", name, e);
                            }
                        });
                    }
                    
                    private static void performCustomCleanup(ApplicationContext context) {
                        // 这里可以添加应用特有的清理逻辑
                        logger.info("Performing custom cleanup tasks");
                        
                        // 例如:保存应用状态
                        // 例如:释放本地资源
                        // 例如:发送关闭通知给其他系统
                    }
                    
                    @Bean
                    public ExecutorService applicationTaskExecutor() {
                        return Executors.newFixedThreadPool(10);
                    }
                    
                    @RestController
                    @RequestMapping("/api")
                    static class ApiController {
                        
                        @Autowired
                        private ExecutorService applicationTaskExecutor;
                        
                        @GetMapping("/task")
                        public String submitTask() {
                            applicationTaskExecutor.submit(() -> {
                                try {
                                    logger.info("Task started, will run for 30 seconds");
                                    Thread.sleep(30000);
                                    logger.info("Task completed");
                                } catch (InterruptedException e) {
                                    logger.info("Task interrupted");
                                    Thread.currentThread().interrupt();
                                }
                            });
                            return "Task submitted";
                        }
                    }
                }
                

                优缺点

                优点:

                • 最大的灵活性和可定制性
                • 可以精确控制资源关闭顺序和方式
                • 适用于复杂应用场景和所有Spring Boot版本
                • 可以处理Spring框架无法管理的外部资源

                  缺点:

                  • 实现复杂度高,需要详细了解应用资源
                  • 维护成本较高
                  • 容易出现资源关闭顺序错误导致的问题

                    适用场景

                    • 具有复杂资源管理需求的应用
                    • 需要特定顺序关闭资源的场景
                    • 使用Spring Boot早期版本(不支持内置优雅停机)
                    • 非Web应用或混合应用架构
                    • 使用了Spring框架不直接管理的资源(如Native资源)

                      方案对比和选择指南

                      下面是三种方案的对比表格,帮助您选择最适合自己场景的实现方式:

                      特性/方案SpringBoot内置Actuator端点自定义ShutdownHook
                      实现复杂度
                      灵活性
                      可定制性
                      框架依赖Spring Boot 2.3+任何Spring Boot版本任何Java应用
                      额外依赖Actuator
                      触发方式系统信号(SIGTERM)HTTP请求系统信号或自定义
                      安全性考虑高(需要保护端点)
                      维护成本
                      适用Web应用最适合适合适合
                      适用非Web应用部分适合部分适合最适合
                      1. 选择SpringBoot内置方案,如果:

                        • 使用Spring Boot 2.3+版本
                        • 主要是标准Web应用
                        • 没有特殊的资源管理需求
                        • 希望最简单的配置
                        • 选择Actuator端点方案,如果:

                          • 需要通过HTTP请求触发停机
                          • 使用早期Spring Boot版本
                          • 集成了运维自动化工具
                          • 已经在使用Actuator进行监控
                          • 选择自定义ShutdownHook方案,如果:

                            • 有复杂的资源管理需求
                            • 需要精确控制停机流程
                            • 使用了Spring框架不直接管理的资源
                            • 混合架构应用(既有Web又有后台作业)

                      结论

                      优雅停机是保障应用可靠性和用户体验的重要实践。SpringBoot提供了多种实现方式,从简单的配置到复杂的自定义实现,可以满足不同应用场景的需求。

                      • 对于简单应用:SpringBoot内置方案是最佳选择,配置简单,足够满足大多数Web应用需求
                      • 对于需要远程触发的场景:Actuator端点提供了HTTP接口控制,便于集成运维系统
                      • 对于复杂应用:自定义ShutdownHook提供了最大的灵活性,可以精确控制资源释放顺序和方式

                        无论选择哪种方式,优雅停机都应该成为微服务设计的标准实践。正确实现优雅停机,不仅能提升系统稳定性,还能改善用户体验,减少因应用重启或降级带来的业务中断。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码