SpringBoot 优雅关闭进程

1,342 阅读2分钟

核心

  • 收到关闭进程的请求后

    • 不再接受 新的请求
    • 等待 已接受的请求 完成
    • 不再执行 新的异步任务
    • 等待 执行中的异步任务 完成
    • 不再执行 新的定时任务
    • 等待 执行中的定时任务 完成
    • 保存数据等
  • 以上操作完成后,关闭进程

具体操作

版本要求

SpringBoot 版本在 2.3.0 以后,Tomcat 版本在 9.0.33 以后。

之前的版本需要自己编写代码,监听信号量事件(关闭进程事件),等待进程清理完成后,才真正关闭。

这里不再赘述。

配置设置

等待请求完成才关闭应用
# >>> 必须的配置 <<<
server:
	# Enable gracefule shutdown
	shutdown: graceful
	# Allow grace timeout period for 20 seconds
	lifecycle:
  	timeout-per-shutdown-phase: 20s
# >>> 非必须的配置 <<<
# 如果配置的话,可以通过 Http 接口来关闭进程。一般不需要。

#监控相关配置
management:
  endpoint:
    # 开启
    shutdown:
      enabled: true
  endpoints:
    web:
      # 只允许shutdown,为了安全,其它想要监控自行配置
      exposure:
        include: "shutdown"
      # 自定义请求路径,为了安全
      base-path: /PATH
  server:
    #自定义请求端口,为了安全
    port: PORT
等待定时任务完成才关闭应用
  • 为定时任务指定线程池
  • 线程池设置执行完毕才关闭
  • 定时任务使用 @Scheduled 和 @Async 注解
@Service
@Slf4f
public class ScheduleTask {
    /**
     * 设置执行定时任务的线程池
     * https://www.cnblogs.com/MarsCheng/p/9734415.html
     *
     * @return
     */
    @Bean
    public TaskScheduler scheduledExecutorService() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(8);
        scheduler.setThreadNamePrefix("scheduled-thread-");
        // https://www.shouxicto.com/article/2896.html
        // 关闭进程前,等待线程执行结束。
        // >>> 定时任务必须加上 @Async 注解,才会使得任务执行后才关闭。<<<
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(30);
        return scheduler;
    }
    
    @Scheduled(cron = "0 0/5 * * * ?")
    @Async
    public void testScheduleTask() {
		log.info("test start..");
        try {
            Thread.sleep(15 * 1000);
        } catch (InterruptedException e) {
            log.error("sleep error, exception=[{}]", e.getMessage());
        }
        log.info("test end...");
    }
}
等待异步任务完成才关闭应用
  • 为异步任务指定线程池
  • 线程池设置执行完毕才关闭
  • 异步任务使用 @Async 注解
  • 关闭进程的等待过程,如果有新的异步任务生成,还会继续执行。需要避免无限地执行下去。
@Configuration
@EnableAsync
@Slf4j
public class AsyncTaskExecutorConfig {
    /**
     * 获取当前机器CPU数量
     */
    private static final int CPU = Runtime.getRuntime().availableProcessors();
    /**
     * 核心线程数(默认线程数)
     */
    private static final int CORE_POOL_SIZE = CPU;
    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = CPU * 2;
    /**
     * 允许线程空闲时间(单位:默认为秒)
     */
    private static final int KEEP_ALIVE_TIME = 60;
    /**
     * 缓冲队列数
     */
    private static final int QUEUE_CAPACITY = 300;
    /**
     * 线程池名前缀
     */
    private static final String THREAD_NAME_PREFIX = "taskExecutor-";


    /**
     * 默认线程池配置执行器
     */
    @Bean("taskExecutor")
    public Executor getAsyncExecutor() {
        // Spring 默认配置是核心线程数大小为1,最大线程容量大小不受限制,队列容量也不受限制。
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        taskExecutor.initialize();

        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        taskExecutor.setRejectedExecutionHandler(new MyRejectedExecutionHandler());
        // https://www.shouxicto.com/article/2896.html
        // 关闭进程前,等待线程执行结束。
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(30);

        return taskExecutor;
    }

    /**
     * 自定义线程队列满时执行的操作
     */
    public static class MyRejectedExecutionHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            log.error("Too many task. task:[{}] rejected by executor:[{}]", r, executor);
        }
    }
}

停止方式

  • IDEA 下启动时,点击 Stop 按钮,[会发看到当前的请求执行完毕后,进程才会关闭。]
  • Linux 下,通过 java -jar xxx.jar 启动时,按下 Ctrl + C,...
  • Linux 下,通过 nohup java -jar xxx.jar启动时,通过 kill PID关闭进程,...
  • 如果配置了 management.health.probes.enabled,任意方式启动进程后,通过访问 http://localhost:PORT/PATH/shutdow ,...

附 Linux kill 简介

  • kill -9 PID 强制停止,进程无条件终止
  • kill PID / kill -15 PID 告诉进程停止,进程可以忽略该信号,或者在执行收尾操作后终止进程
  • kill -2 PID

Ref