SpringBoot源码解读与原理分析(四十一)SpringBoot 2.3新特性:优雅停机

本文介绍了SpringBoot2.3引入的优雅停机功能,如何在应用关闭时确保正在进行的请求完成,以及其原理和在不同Web容器中的应用。通过示例展示了如何配置和测试优雅停机,并详细剖析了嵌入式Tomcat的优雅停机实现过程。
摘要由CSDN通过智能技术生成

前言

在SpringBoot应用需要停机时,如果直接关闭应用,会导致部分正在处理中的请求被强制中断,这在某些业务场景中会产生脏数据。

为了解决这一问题,SpringBoot 2.3中引入了“优雅停机”的新特性。

14.4 SpringBoot 2.3新特性:优雅停机

优雅停机”的新特性,是指在SpringBoot应用被关闭时(注意此处的关闭可以是kill -2,但不能是kill -9),会预留一小段时间,使应用内部的业务线程执行完毕。此时嵌入式Web容器不允许有新的请求进入,以此达到优雅停机的效果。

14.4.1 测试优雅停机场景

编写一个Controller方法,使线程休眠10s来模拟业务逻辑的处理时间很长:

@RestController
public class GracefulTestController {
    
    public String test() throws InterruptedException {
        System.out.println("开始时间:" + new Date());
        TimeUnit.SECONDS.sleep(10);
        System.out.println("结束时间:" + new Date());
        return "success";
    }
}

默认情况下,SpringBoot的停机方式是立即停机(immediate),若想启用优雅停机,需要在application.properties中配置server.shutdown=graceful。此外还可以通过配置spring.lifecycle.timeout-per-shutdown-phase属性,自定义缓冲时间(默认30s)。

编写主启动类,借助Scanner实现控制台软退出:

@SpringBootApplication
public class WebMvcApp {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(WebMvcApp.class, args);

        // 借助Scanner实现控制台软退出
        // 当在控制台手动输入exit字符串时,退出应用
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String input = scanner.nextLine();
            if ("exit".equals(input)) {
                break;
            }
        }
        SpringApplication.exit(context);
    }

}

启动项目,在浏览器访问http://localhost:8080/test,正常情况下,浏览器在等待10s后会接收到"success"的字符串响应。

开始时间:Mon Mar 04 22:57:58 CST 2024
结束时间:Mon Mar 04 22:58:08 CST 2024

如果在请求处理的过程中,手动在控制台输入字符串exit以停止SpringBoot应用,可以发现浏览器仍然会等待响应并接收到"success"的字符串响应。随后,SpringBoot应用才会停止。

开始时间:Mon Mar 04 23:00:10 CST 2024
exit
Commencing graceful shutdown. Waiting for active requests to complete
结束时间:Mon Mar 04 23:00:20 CST 2024
Graceful shutdown complete
Shutting down ExecutorService 'applicationTaskExecutor'

从控制台的输出信息可知,优雅停机测试成功。

另外,不同的嵌入式Web容器在优雅停机期间应对客户端新请求的响应策略不同:嵌入式Tomcat和Netty不会接收请求,客户端会响应超时;嵌入式Undertow则会直接响应503错误。

14.4.2 优雅停机的实现原理

以嵌入式Tomcat为例:

源码1TomcatServletWebServerFactory.java

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    // 初始化优雅停机的回调钩子
    this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
    initialize();
}
源码2AbstractConfigurableWebServerFactory.java

private Shutdown shutdown = Shutdown.IMMEDIATE;
public Shutdown getShutdown() {
    return this.shutdown;
}

由 源码1-2 可知,在TomcatServletWebServerFactory创建TomcatWebServer时,第三个参数是getShutdown方法返回的Shutdown类。而Shutdown类是一个枚举类,getShutdown方法默认返回立即停机,即Shutdown.IMMEDIATE。

此处的shutdown属性则对应了application.properties文件中的server.shutdown配置。因此上面的测试案例中,该属性已被修改为graceful。

SpringBoot源码解读与原理分析(二十七)嵌入式Tomcat 8.3.2 Web容器关闭相关的回调 中提到,WebServerGracefulShutdownLifecycle用于触发嵌入式Web容器优雅停机的核心生命周期回调,它可以在IOC容器销毁阶段回调其stop方法以触发销毁逻辑。而其stop方法会回调WebServer的shutDownGracefully方法实现优雅停机。

源码3WebServerGracefulShutdownLifecycle.java

@Override
public void stop(Runnable callback) {
    this.running = false;
    this.serverManager.shutDownGracefully(callback);
}
源码4WebServerManager.java

void shutDownGracefully(Runnable callback) {
    this.webServer.shutDownGracefully((result) -> callback.run());
}
源码5TomcatWebServer.java

@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
    if (this.gracefulShutdown == null) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
        return;
    }
    // 此处会执行优雅停机
    this.gracefulShutdown.shutDownGracefully(callback);
}

由 源码3-5 可知,嵌入式Tomcat容器的优雅停机最终会调用TomcatWebServer的shutDownGracefully方法。

源码6GracefulShutdown.java

void shutDownGracefully(GracefulShutdownCallback callback) {
    logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
    // 启动一个新的线程
    new Thread(() -> doShutdown(callback), "tomcat-shutdown").start();
}

private void doShutdown(GracefulShutdownCallback callback) {
    // 关闭Connector,服务失去接收请求的能力
    List<Connector> connectors = getConnectors();
    connectors.forEach(this::close);
    try {
        for (Container host : this.tomcat.getEngine().findChildren()) {
            for (Container context : host.findChildren()) {
                // 每隔50s检查一次Container是否停止
                while (isActive(context)) {
                    if (this.aborted) {
                        // logger ...
                        callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
                        return;
                    }
                    Thread.sleep(50);
                }
            }
        }

    } // catch ...
    logger.info("Graceful shutdown complete");
    callback.shutdownComplete(GracefulShutdownResult.IDLE);
}

由 源码6 可知,shutDownGracefully方法完成的工作是延迟关闭嵌入式Web容器。它会在内部启动一个新的线程,执行doShutdown方法。

doShutdown方法首先会关闭Connector,由此Tomcat就失去了接收新请求的能力;随后该方法会提取出嵌入式Tomcat中所有Engine中的所有Container,每隔50s检查一次Container是否停止,当所有Context中的线程全部执行完毕,即Context全部停止时,优雅停机流程执行完毕。

14.5 小结

第14章到此就梳理完毕了,本章的主题是:运行SpringBoot应用。回顾一下本章的梳理的内容:

(四十)基于jar/war包的运行机制
(四十一)SpringBoot 2.3新特性:优雅停机

更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

至此,完结撒花,该专栏的内容全部学习完毕,后续将根据实际情况添加新的内容。

  • 33
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值