java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot优雅停机方式

SpringBoot实现优雅停机的三种方式

作者:风象南

优雅停机(Graceful Shutdown)是指应用在接收到停止信号后,能够妥善处理现有请求、释放资源,然后再退出的过程,本文将详细介绍SpringBoot中实现优雅停机的三种方式,需要的朋友可以参考下

引言

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

什么是优雅停机?

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

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

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

原理与支持版本

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

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

配置方法

application.propertiesapplication.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<ContextClosedEvent> contextClosedEventListener() {
        return event -> logger.info("Received spring context closed event - shutting down");
    }
}

测试验证

优缺点

优点:

缺点:

适用场景

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

原理与实现

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

配置步骤

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
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

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

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<ContextClosedEvent> 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");
        };
    }
}

优缺点

优点:

缺点:

适用场景

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

原理与实现

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

基本实现步骤

完整实现示例

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

@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<String, ExecutorService> 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<String, DataSource> 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";
        }
    }
}

优缺点

优点:

缺点:

适用场景

方案对比和选择指南

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

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

结论

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

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

以上就是SpringBoot实现优雅停机的三种方式的详细内容,更多关于SpringBoot优雅停机方式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文