核心
-
收到关闭进程的请求后
- 不再接受 新的请求
- 等待 已接受的请求 完成
- 不再执行 新的异步任务
- 等待 执行中的异步任务 完成
- 不再执行 新的定时任务
- 等待 执行中的定时任务 完成
- 保存数据等
-
以上操作完成后,关闭进程
具体操作
版本要求
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
- Spring Boot 2.3.0配置Graceful-Shutdown,Readiness和Liveness www.kubernetes.org.cn/7788.html
- spring boot不同版本的优雅关闭(graceful shutdown)和在windows下winsw服务方式运行的配置 www.1024sou.com/article/449…
- www.shouxicto.com/article/289…
- Java 优雅地退出程序 jiyiren.github.io/2018/06/18/…
- 研究优雅停机时的一点思考 www.cnkirito.moe/gracefully-…
- Spring 及 Spring Boot 进程优雅停止方式 blog.csdn.net/TRAMP_ZZY/a…