Spring Boot應用中的優(yōu)雅關閉:同步與異步任務的處理方法
如果你的应用在某个进程尚未完成时就被终止,你可能会遇到严重的问题。尤其是对于异步进程,这种情况更为突出,它们需要更小心地处理。在这篇文章里,我将讨论如何优雅地关闭应用程序,不论是同步任务还是异步任务。
什么是优雅停机呢?Spring Boot 应用的优雅关闭指的是以一种允许应用完成任何正在进行的任务并正确释放资源并完成所有任务的方式停止应用的过程。这确保了在途请求或后台任务能够完成,并维护数据的完整性。关闭过程在 Spring Boot 应用上下文关闭时启动,通常发生在关闭阶段。此阶段通常由各种事件触发,例如接收到终止信号、程序退出或主方法执行完毕。
_你需要记住,仅仅配置你的应用程序并不能保证优雅地停止。 ,例如,在 Kubernetes 中,你需要设置 terminationGracePeriod
来指定接收到 SIGTERM 信号以及被强制终止的 SIGKILL 信号之间的间隔时间。
我已经将这篇帖子测试的代码上传到了Github。简而言之,有两个端点:同步端点和异步端点。每个函数都会打印一个数字,并在一个休眠1秒的循环中执行,循环迭代10,000次。
这里有一个脚本用来测试程序的优雅关闭:
# sigterm_script.sh
curl localhost:8080/v1/samples/sync &
sleep 1
PID=$(lsof -t -i:8080 | head -n 1)
if [ -z "$PID" ]; then
echo "应用程序未运行"
else
echo "正在终止进程ID为 ${PID} 的应用程序"
kill -15 "$PID" #发送SIGTERM信号给PID进程
fi
同步任务设置
优雅地关闭服务器程序 服务端:
关闭方式: 优雅
要配置优雅地关闭,请将 server.shutdown
设置为 graceful
。该设置决定了 Spring Boot 应用中的 Tomcat 服务器是否也会优雅地关闭。
// org.springframework.boot.web.embedded.tomcat.GracefulShutdown
private void doShutdown(GracefulShutdownCallback callback, CountDownLatch shutdownUnderway) {
try {
List<Connector> connectors = getConnectors();
connectors.forEach(this::close);
shutdownUnderway.countDown();
try {
for (Container host : this.tomcat.getEngine().findChildren()) {
for (Container context : host.findChildren()) {
while (!this.aborted && isActive(context)) {
Thread.sleep(50);
}
if (this.aborted) {
logger.info("优雅的关闭被终止,因为有一个或多个请求仍然运行");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
return;
}
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
logger.info("优雅的关闭已经完成");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
finally {
shutdownUnderway.countDown();
}
}
如果优雅关闭被启用,这段代码将会运行。它将停止接收新请求,等待所有活跃请求完成,并处理任何中断或终止信号。
每个关机阶段的启动关闭生命周期超时设置 spring:
生命周期:
每个关闭阶段的超时时长: 90s
timeout-per-shutdown-phase
属性设定了 Spring 在每个阶段内等待 beans 完成关闭的最大时间。如果某个阶段的 beans 在指定时间内未能成功关闭,Spring 将会继续到下一阶段。默认情况下是,这个超时时间为 30 秒。
// org.springframework.context.support.DefaultLifecycleProcessor.LifecycleGroup
public void stop() {
...
try {
latch.await(this.timeout, TimeUnit.MILLISECONDS);
if (latch.getCount() > 0 && !countDownBeanNames.isEmpty() && logger.isInfoEnabled()) {
logger.info("阶段结束 " + this.phase + ",在超时 " + this.timeout + " 毫秒后仍处于运行状态的 bean 有:" + countDownBeanNames.size() +
" 个:" + countDownBeanNames);
}
}
...
timeout-per-shutdown-phase
在 LifecycleGroup
的停止方法中被使用,用于指定每个关闭阶段里,由 timeout
变量定义的最长时间,让各个 bean 在每个阶段中停止。
...
信息 10393 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : 正在开始优雅的关闭。等待活跃的请求完成
信息 10393 --- [nio-8080-exec-1] org.example.service.SampleService : 775
信息 10393 --- [nio-8080-exec-1] org.example.service.SampleService : 776
...
信息 10393 --- [nio-8080-exec-1] org.example.service.SampleService : 9997
信息 10393 --- [nio-8080-exec-1] org.example.service.SampleService : 9998
信息 10393 --- [nio-8080-exec-1] org.example.service.SampleService : 9999
信息 10393 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : 优雅的关闭已经完成
进程因信号15(SIGTERM)中断,退出码为143
上述日志显示,在进程仍在进行中,应用程序收到了SIGTERM信号并开始优雅地结束。应用程序继续运行直到正在进行的进程完成,然后才执行关闭。
异步任务的配置 @Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setAwaitTerminationSeconds(AWAIT_TERMINATION_SECONDS);
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
在使用异步任务时,需要在任务执行器(例如 ThreadPoolTaskExecutor
)中进行额外的配置设置。设置 waitForTasksToCompleteOnShutdown
可以让执行器在终止前等待所有活动任务执行完毕。awaitTerminationSeconds
指定了在发起关闭后等待剩余任务完成的最长时间。
// org.springframework.scheduling.concurrent.ExecutorConfigurationSupport
public void shutdown() {
if (logger.isDebugEnabled()) {
logger.debug("正在关闭 ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
for (Runnable remainingTask : this.executor.shutdownNow()) {
取消剩余任务(remainingTask);
}
}
awaitTerminationIfNecessary(this.executor);
}
}
在 ExecutorConfigurationSupport
类的 shutdown
方法中,如果 waitForTasksToCompleteOnShutdown
参数被设置为真,则调用 executor.shutdown()
;否则,调用 executor.shutdownNow()
。
shutdown
方法会开始一个有序关闭,停止接受新的任务并允许现有任务完成,而 shutdownNow
则会立即尝试停止所有正在运行的任务,并阻止等待的任务继续处理。
首先,awaitTerminationIfNecessary
方法会等待指定的 awaitTerminationSeconds
时间。因此,你需要配置这两个参数,即 awaitTerminationSeconds
和 waitForTasksToCompleteOnShutdown
,以确保在关闭前等待任务完成。否则,它会立即关闭,而不会等待任务完成。
// org.springframework.scheduling.concurrent.ExecutorConfigurationSupport
@Override // 重写父类的方法
public void destroy() { // 销毁方法,用于执行清理操作
shutdown(); // 执行关闭操作
}
重要的是要指出,shutdown
方法是在 destroy
方法中被调用的,而 destroy
方法则来自重写的 DisposableBean
。这意味着在使用 DelegatingSecurityContextExecutor
时,必须将 ThreadPoolTaskExecutor
定义为一个独立的 bean。如果不这样做,如下面的错误配置所示,shutdown
方法将不会在 Spring bean 的生命周期内被正确调用,这可能会导致执行程序未能按预期关闭。
- 正确的设置
@Bean
public ThreadPoolTaskExecutor 线程池任务执行器() {
ThreadPoolTaskExecutor 线程池 = new ThreadPoolTaskExecutor();
线程池.setTaskDecorator(new MDCTaskDecorator());
线程池.setWaitForTasksToCompleteOnShutdown(true);
线程池.setAwaitTerminationSeconds(AWAIT_TERMINATION_SECONDS);
线程池.initialize();
return 线程池;
}
@Bean
public Executor 任务执行器(final ThreadPoolTaskExecutor 线程池) {
return new DelegatingSecurityContextExecutor(线程池);
}
- 配置错误
@Bean
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setTaskDecorator(new MDCTaskDecorator());
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setAwaitTerminationSeconds(AWAIT_TERMINATION_SECONDS);
threadPoolTaskExecutor.initialize();
return new DelegatingSecurityContextExecutor(threadPoolTaskExecutor);
}
看看结果怎么样
INFO 10352 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : 正在开始优雅的关闭过程。等待所有正在进行的请求完成
INFO 10352 --- [lTaskExecutor-1] org.example.service.SampleService : 2349
INFO 10352 --- [lTaskExecutor-1] org.example.service.SampleService : 2350
INFO 10352 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : 优雅关闭已完成
INFO 10352 --- [lTaskExecutor-1] org.example.service.SampleService : 2351
...
INFO 10352 --- [lTaskExecutor-1] org.example.service.SampleService : 9997
INFO 10352 --- [lTaskExecutor-1] org.example.service.SampleService : 9998
INFO 10352 --- [lTaskExecutor-1] org.example.service.SampleService : 9999
进程因信号 15:SIGTERM 被中断,退出码为 143
与同步任务不同,对于异步任务,你可以在异步任务还在执行中时看到优雅的关机信息。关机过程才会在所有异步任务完成后结束。
最后确保 Spring Boot 应用程序优雅地结束对于维护数据完整性并完成正在进行的任务至关重要。按照本文中的指南来做,您可以更有效地管理关闭过程,确保所有任务在应用终止前完成。
我真的希望这篇文章能成为你努力中的宝贵资源。感谢你花时间读我的文章 😊 如果你发现任何不准确或有任何想法想要分享,请随时告诉我一声。你的反馈对我们很有帮助,让我们可以不断学习和改进。
共同學習,寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章