SpringBoot优雅停机 actuator/shutdown
springboot 优雅停机 一般有两种方案 ,今天讲下 ``- actuator/shutdown
实现原理
kill -15 $pid
actuator/shutdown
/**
* {@link Endpoint @Endpoint} to shutdown the {@link ApplicationContext}.
*
* @author Dave Syer
* @author Christian Dupuis
* @author Andy Wilkinson
* @since 2.0.0
*/
@Endpoint(id = "shutdown", enableByDefault = false)
public class ShutdownEndpoint implements ApplicationContextAware {
private static final Map<String, String> NO_CONTEXT_MESSAGE = Collections
.unmodifiableMap(Collections.singletonMap("message", "No context to shutdown."));
private static final Map<String, String> SHUTDOWN_MESSAGE = Collections
.unmodifiableMap(Collections.singletonMap("message", "Shutting down, bye..."));
private ConfigurableApplicationContext context;
@WriteOperation
public Map<String, String> shutdown() {
if (this.context == null) {
return NO_CONTEXT_MESSAGE;
}
try {
return SHUTDOWN_MESSAGE;
}
finally {
// 开一个异步线程
Thread thread = new Thread(this::performShutdown);
thread.setContextClassLoader(getClass().getClassLoader());
thread.start();
}
}
private void performShutdown() {
try {
// 休眠500毫秒
Thread.sleep(500L);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
// 关闭spring 容器
this.context.close();
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) context;
}
}
}
底层原理其实就是关闭 spring ConfigurableApplicationContext.close();
上下文。如果我们有web 服务只这样设置其实是不行的 比如我们 http请求正在执行 此时 spring 被关闭 那么正在执行的请求可能无法完成 所以还要 设置 web优雅停机
SpringBoot优雅停机Web Server
Springboot-2.3.0开始提供了官方的优雅停机方案,当配置 server.shutdown=graceful
时,Spring Boot 应用在接收到停机信号后,将尝试完成当前正在处理的请求,同时停止接收新的请求,然后再关闭应用程序。这样做的目的是为了减少停机对用户和业务的影响。
配置spring.lifecycle.timeout-per-shutdown-phase=30s
这个配置表示在强制关闭之前,Spring Boot会等待30秒以完成当前活跃的请求。
优雅停机是微服务架构中非常重要的一个特性,因为它可以在服务需要升级、重新部署或者维护时,最小化对正在进行的业务操作的影响。通过优雅停机,可以减少服务不可用的时间,提高用户体验。那我们首先来看下需要怎么使用呢?首先需要在配置文件中配置优雅停机,如下:
server:
shutdown: graceful ## 开启优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 5s ## 优雅停机等待时间,默认30s
配置完成后,我们就可以启动项目来进行试验了。具体的项目代码示例我就不贴了,需要注意的是我们在停机的时候不能使用kill -9来强制关闭,这样优雅停机是不起作用的。我们可以使用actuator提供的/shutdown端口来关闭服务。此时应用日志中应该会有如下的日志:
[SpringContextShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
[tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
如果在等待30s后还有请求没有处理完成,那么日志中也会体现出来,如下:
[SpringContextShutdownHook] o.s.c.support.DefaultLifecycleProcessor : Failed to shut down 1 bean with phase value 2147483647 within timeout of 5000: [webServerGracefulShutdown]
[tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown aborted with one or more requests still active
源码解析
优雅停机后 执行ConfigurableApplicationContext.close()
方法
org.springframework.context.support.AbstractApplicationContext#close
org.springframework.context.support.DefaultLifecycleProcessor#onClose
方法 默认实现
@Override
public void onClose() {
stopBeans();
this.running = false;
}
核心方法
private void stopBeans() {
// 得到 三个 Lifecycle
// 1. "webServerGracefulShutdown"
//2. "springBootLoggingLifecycle"
//3. "webServerStartStop"
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new HashMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
int shutdownPhase = getPhase(bean);
LifecycleGroup group = phases.get(shutdownPhase);
if (group == null) {
// 构造 LifecycleGroup =3
group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
phases.put(shutdownPhase, group);
}
group.add(beanName, bean);
});
if (!phases.isEmpty()) {
List<Integer> keys = new ArrayList<>(phases.keySet());
// 排序
keys.sort(Collections.reverseOrder());
for (Integer key : keys) {
phases.get(key).stop();
}
}
}
- "webServerGracefulShutdown
- 处理wbe 容器 优雅停机逻辑
org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle
- "springBootLoggingLifecycle"
- 处理日志关闭
org.springframework.boot.context.logging.LoggingApplicationListener.Lifecycle#stop
- "webServerStartStop" ->
- 处理容器关闭
org.springframework.boot.web.embedded.tomcat.TomcatWebServer
进入 stop方法org.springframework.context.support.DefaultLifecycleProcessor.LifecycleGroup#stop
public void stop() {
if (this.members.isEmpty()) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Stopping beans in phase " + this.phase);
}
this.members.sort(Collections.reverseOrder());
CountDownLatch latch = new CountDownLatch(this.smartMemberCount);
Set<String> countDownBeanNames = Collections.synchronizedSet(new LinkedHashSet<>());
Set<String> lifecycleBeanNames = new HashSet<>(this.lifecycleBeans.keySet());
for (LifecycleGroupMember member : this.members) {
if (lifecycleBeanNames.contains(member.name)) {
// 核心方法
doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
}
else if (member.bean instanceof SmartLifecycle) {
// Already removed: must have been a dependent bean from another phase
latch.countDown();
}
}
try {
latch.await(this.timeout, TimeUnit.MILLISECONDS);
if (latch.getCount() > 0 && !countDownBeanNames.isEmpty() && logger.isInfoEnabled()) {
logger.info("Failed to shut down " + countDownBeanNames.size() + " bean" +
(countDownBeanNames.size() > 1 ? "s" : "") + " with phase value " +
this.phase + " within timeout of " + this.timeout + "ms: " + countDownBeanNames);
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
这个bean org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle#stop(java.lang.Runnable)
org.springframework.boot.web.embedded.tomcat.TomcatWebServer#shutDownGracefully
这里的容器我用的 tomcat org.springframework.boot.web.embedded.tomcat.GracefulShutdown#shutDownGracefully
private void doShutdown(GracefulShutdownCallback callback) {
List<Connector> connectors = getConnectors();
// 获取所有连接器给关闭
connectors.forEach(this::close);
try {
// 遍历Tomcat引擎中所有的Host(虚拟主机)
for (Container host : this.tomcat.getEngine().findChildren()) {
// 遍历每个Host中的所有Context(Web应用程序)
for (Container context : host.findChildren()) {
// 检查当前Context是否仍然有活跃的请求
while (isActive(context)) {
// 如果在关闭过程中决定中断优雅关闭
if (this.aborted) {
logger.info("Graceful shutdown aborted with one or more requests still active");
// 调用回调函数,通知调用者优雅关闭未能完成,因为还有活跃的请求
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
return;
}
// 如果还有活跃的请求,线程等待50毫秒后再次检查
Thread.sleep(50);
}
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
优雅停机关闭后 才会处理关闭 bean beanFactory