Spring Boot 优雅关闭
Posted
技术标签:
【中文标题】Spring Boot 优雅关闭【英文标题】:Spring Boot graceful shutdown 【发布时间】:2019-09-26 04:12:52 【问题描述】:我正在开发一个由嵌入式 Tomcat 支持的 Spring Boot 应用程序,我需要通过以下步骤开发一个优雅的关闭:
-
停止处理新的 HTTP 请求(停止 Web 容器)
处理所有已接受的请求
关闭 Spring ApplicationContext
*按顺序执行上述步骤(一个接一个)
我怎样才能做到这一点?
附: Spring Boot 1.5.20.RELEASE,Java 8
【问题讨论】:
你试过 ApplicationListener 和 context.close() 了吗? 这个链接可能对你有帮助 (dzone.com/articles/graceful-shutdown-spring-boot-applications) afaik 只需为 jdbc 等所有资源注册一个 destroyMethod,当您使用 kill 命令简单地杀死应用程序时,spring boot 将为您完成剩下的工作 @AnirudhSimha spring 不会在进程 kill 上调用 destroy 方法,它们仅在调用上下文的 close 方法后调用 我在 HA Proxy 和 Ansible 的帮助下做了同样的事情。我的步骤是 1) 启用保留页面,因此不再收到请求 2) 检查服务日志,从过去 5 分钟开始不再轮换。 3)然后关机 【参考方案1】:它很简单,Spring boot 本身就提供了功能。 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-application-exit
void shoutdown()
System.out.println("====+= Shoutdown +++===");
System.exit(SpringApplication.exit(apc, this.exitCodeGenerator()));
您可以在输出中看到所有当前线程都已关闭。 输出:
====+= 大喊+++===
2020-06-09 11:21:45,543
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ExitCodeEvent 2020-06-09 11:21:45,543
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ExitCodeEvent 2020-06-09 11:21:45,546
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ContextClosedEvent 2020-06-09 11:21:45,546
DEBUG[main][c.u.j.c.EnableEncryptablePropertiesBeanFactoryPostProcessor]
Application Event Raised: ContextClosedEvent 2020-06-09 11:21:45,547
INFO [main][o.a.kafka.clients.producer.KafkaProducer] [Producer
clientId=producer-1] Closing the Kafka producer with timeoutMillis =
30000 ms. 2020-06-09 11:21:45,548
DEBUG[kafka-producer-network-thread |
producer-1][o.a.k.clients.producer.internals.Sender] [Producer
clientId=producer-1] Beginning shutdown of Kafka producer I/O thread,
sending remaining records. 2020-06-09 11:21:45,551
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name connections-closed: 2020-06-09 11:21:45,554
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name connections-created: 2020-06-09 11:21:45,554
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name successful-authentication: 2020-06-09 11:21:45,558
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name failed-authentication: 2020-06-09 11:21:45,558
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name bytes-sent-received: 2020-06-09 11:21:45,559
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name bytes-sent: 2020-06-09 11:21:45,559
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name bytes-received: 2020-06-09 11:21:45,560
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name select-time: 2020-06-09 11:21:45,561
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name io-time: 2020-06-09 11:21:45,570
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node--1.bytes-sent 2020-06-09 11:21:45,570
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node--1.bytes-received 2020-06-09 11:21:45,570
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node--1.latency 2020-06-09 11:21:45,571
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node-0.bytes-sent 2020-06-09 11:21:45,571
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node-0.bytes-received 2020-06-09 11:21:45,573
DEBUG[kafka-producer-network-thread |
producer-1][org.apache.kafka.common.metrics.Metrics] Removed sensor
with name node-0.latency 2020-06-09 11:21:45,573
DEBUG[kafka-producer-network-thread |
producer-1][o.a.k.clients.producer.internals.Sender] [Producer
clientId=producer-1] Shutdown of Kafka producer I/O thread has
completed. 2020-06-09 11:21:45,607
DEBUG[main][o.a.kafka.clients.producer.KafkaProducer] [Producer
clientId=producer-1] Kafka producer has been closed 2020-06-09
11:21:45,611 DEBUG[main][o.hibernate.internal.SessionFactoryImpl]
HHH000031: Closing 2020-06-09 11:21:45,611
DEBUG[main][o.h.type.spi.TypeConfiguration$Scope] Un-scoping
TypeConfiguration
[org.hibernate.type.spi.TypeConfiguration$Scope@5dfd31f4] from
SessionFactory [org.hibernate.internal.SessionFactoryImpl@62a54948]
2020-06-09 11:21:45,612
DEBUG[main][o.h.s.i.AbstractServiceRegistryImpl] Implicitly
destroying ServiceRegistry on de-registration of all child
ServiceRegistries 2020-06-09 11:21:45,613
DEBUG[main][o.h.b.r.i.BootstrapServiceRegistryImpl] Implicitly
destroying Boot-strap registry on de-registration of all child
ServiceRegistries 2020-06-09 11:21:45,613 INFO
[main][com.zaxxer.hikari.HikariDataSource] HikariPool-1 - Shutdown
initiated... 2020-06-09 11:21:45,754 INFO
[main][com.zaxxer.hikari.HikariDataSource] HikariPool-1 - Shutdown
completed.
【讨论】:
【参考方案2】:Spring Boot 2.3(2020 年 5 月发布)中添加了正常关闭支持。这允许在关闭上下文和关闭容器之前完成活动请求。
启用优雅关机后,应用程序将在关机时依次执行以下步骤:
停止接受新请求 将等待一些可配置时间来处理已接受的请求 停止容器 停止嵌入式服务器来自release notes:
所有四个嵌入式 Web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反应式和基于 Servlet 的 Web 应用程序都支持优雅关闭。当使用
server.shutdown=graceful
启用时,在关闭时,Web 服务器将不再允许新请求,并将等待活动请求完成的宽限期。可以使用spring.lifecycle.timeout-per-shutdown-phase
配置宽限期。
要启用正常关机,请将
server.shutdown=graceful
添加到属性中(默认设置为immediate
)。
可以使用spring.lifecycle.timeout-per-shutdown-phase
属性配置宽限期(例如:spring.lifecycle.timeout-per-shutdown-phase=1m
。
对于 Spring Boot this Spring GitHub issue 中所述。
【讨论】:
【参考方案3】:我最终得到了:
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent>
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
@Override
public void customize(Connector connector)
this.connector = connector;
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent)
log.info("Protocol handler is shutting down");
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor)
try
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS))
log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
else
log.info("Protocol handler shut down");
catch (InterruptedException e)
Thread.currentThread().interrupt();
还有一些额外的 bean:
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
...
@Bean
public GracefulShutdown gracefulShutdown()
return new GracefulShutdown();
@Bean
public EmbeddedServletContainerFactory servletContainer(final GracefulShutdown gracefulShutdown)
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
...
【讨论】:
具有“类似”解决方案的博客:blog.marcosbarbero.com/graceful-shutdown-spring-boot-apps 这是用于优雅关闭 servlet 容器的 Spring Boot 问题的链接:github.com/spring-projects/spring-boot/issues/4657 看看这个解决方案,我的理解是,它会在要求关机后强制等待 30 秒,并且只有在 30 秒后,才会优雅地关机。但是 OP 也希望停止请求进入。您的解决方案如何实现这一目标? 这可以在没有 spring-boot 的 spring mvc webapp 中实现吗?以上是关于Spring Boot 优雅关闭的主要内容,如果未能解决你的问题,请参考以下文章
从 Shell 脚本优雅地关闭 Spring Boot 应用程序