看了一下源码,发现Spring Boot的优雅关闭功能是如何在内部调用的

首先

Spring Boot 2.3发布了Graceful shutdown的功能。只需设置应用程序属性即可轻松实现Graceful shutdown。但是,由于它容易实现,内部调用和实现方式就变得相当黑盒子了。

因此,本文将通过对Spring Boot的源代码进行阅读,探究Graceful shutdown是如何在内部被调用的,并写下了研究结果。

總結

    • Spring BootのGraceful shutdownが内部でどう呼ばれているか

SpringのSmartLifecycleという仕組みによってApplicationContextのclose時に処理が呼ばれる

这次要使用的版本服务器

我正在使用Spring Boot 2.3.1版本,并且服务器使用Jetty。

前提:在Spring Boot中实现优雅关机的方法

只要将应用程序属性设置为以下方式,就可以实现优雅关闭。

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s #サーバの最大シャットダウン時間 この時間までにリクエストが0にならなかったら強制的にサーバを停止する

具体而言,Graceful shutdown指的是以下行为:

    在处理完当前的请求之前,不接受新的请求,并且不会停止服务器。

不同的Web服务器对于“不接受新请求”这一行为会有不同的行为。

Webサーバ挙動Tomcat
Jetty
Reactor Nettyネットワークレイヤーでコネクションを受け付けないUndertow503を返す

参考:Spring Boot参考文档#启动特性-优雅关闭

Graceful shutdown在内部是如何调用的。

我从上面了解到了Spring Boot的优雅关闭是如何工作的。那么这个过程是从哪里调用的呢?
我想阅读源代码来确认一下。
请注意:本次追踪的代码是基于Spring Boot 2.3.1的。由于内部行为可能会在将来的版本升级中发生改变,请注意。
另外,服务器使用的是Jetty。

确定优雅关闭的实际处理部分。

首先,我希望通过找到Graceful shutdown的实际处理部分,并从那里开始追踪调用者来执行策略。
无论如何,让我们尝试运行Graceful shutdown。
首先启动Spring Boot。

% ./mvnw spring-boot:run
...
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)
...
2020-07-30 02:35:10.626  INFO 15060 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@552ed807{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2020-07-30 02:35:10.628  INFO 15060 --- [           main] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8080 (http/1.1) with context path '/'
2020-07-30 02:35:10.640  INFO 15060 --- [           main] c.k.s.SampleGracefulShutdownApplication  : Started SampleGracefulShutdownApplication in 1.517 seconds (JVM running for 1.795)

我可以看到Jetty正在运行。
这次我将使用Spring Boot Actuator的shutdown端点来停止它。

2020-07-30 02:35:14.253  INFO 15060 --- [     Thread-223] o.s.b.web.embedded.jetty.JettyWebServer  : Commencing graceful shutdown. Waiting for active requests to complete
2020-07-30 02:35:14.258  INFO 15060 --- [ jetty-shutdown] o.s.b.web.embedded.jetty.JettyWebServer  : Graceful shutdown complete
2020-07-30 02:35:14.264  INFO 15060 --- [     Thread-223] o.e.jetty.server.AbstractConnector       : Stopped ServerConnector@552ed807{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2020-07-30 02:35:14.265  INFO 15060 --- [     Thread-223] org.eclipse.jetty.server.session         : node0 Stopped scavenging
2020-07-30 02:35:14.267  INFO 15060 --- [     Thread-223] o.e.j.s.h.ContextHandler.application     : Destroying Spring FrameworkServlet 'dispatcherServlet'
2020-07-30 02:35:14.268  INFO 15060 --- [     Thread-223] o.e.jetty.server.handler.ContextHandler  : Stopped o.s.b.w.e.j.JettyEmbeddedWebAppContext@38ed139b{application,/,[file:///private/var/folders/ds/ky2m710d41ldw12zs6jhg4jh0000gn/T/jetty-docbase.10343674703387388779.8080/],UNAVAILABLE}
2020-07-30 02:35:14.270  INFO 15060 --- [     Thread-223] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

看第一行日志可知,正在进行Graceful shutdown;而看第二行日志可知,已经完成了Graceful shutdown。
负责输出这段日志的两个类都是o.s.b.web.embedded.jetty.JettyWebServer类。
看起来这个类似乎与Graceful shutdown有关,所以接下来我会调查一下这个类。

当查看JettyWebServer类时,可以看到存在一个名为shutDownGracefully的方法。这个方法内部应该会执行Graceful shutdown。

@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
    if (this.gracefulShutdown == null) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
        return;
    }
    this.gracefulShutdown.shutDownGracefully(callback);
}

进一步观察,根据this.gracefulShutdown实例是否为null,可以确定是否进行优雅关机。如果this.gracefulShutdown实例不为null,则很可能会调用Graceful shutdown处理。

当查看此方法this.gracefulShutdown.shutDownGracefully(callback)的内部时,发现如下所示。

void shutDownGracefully(GracefulShutdownCallback callback) {
    logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
    for (Connector connector : this.server.getConnectors()) {
        shutdown(connector);
    }
    this.shuttingDown = true;
    new Thread(() -> awaitShutdown(callback), "jetty-shutdown").start();

}

logger.info()在第一行中的信息与最初停止Spring Boot时记录的日志内容相匹配。
因此,几乎可以确定此方法内部已经执行了优雅关机的实际处理。

如果提到这一点,由于我已在这篇文章中介绍了内部代码,所以本篇文章将省略此部分。

上面的处理是从哪里调用的?

到目前为止,我们已成功达到了优雅关机的实际处理部分。那么,这个处理是从哪里被调用的呢?

当跟踪最初展示的JettyWebServer类中的shutDownGracefully方法时,我们发现它是从以下的WebServerGracefulShutdownLifecycle类的stop方法中调用的。

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {

    ...

    @Override
    public void stop(Runnable callback) {
        this.running = false;
        this.webServer.shutDownGracefully((result) -> callback.run());
    }
}

当你关注这个类时,可以看出它实现了SmartLifecycle接口。
实际上,这个SmartLifecycle是本次的关键。
实现了SmartLifecycle接口的类在ApplicationContext启动和关闭时将调用任意的处理。具体来说,会调用SmartLifecycle的start和stop方法。
本次的优雅关闭是通过重写并调用SmartLifecycle的stop方法来实现的。

    SmartLifecycle (Spring Framework 5.2.7.RELEASE API)

总之,Spring Boot的Graceful shutdown是通过SmartLifecycle的stop方法调用的。
顺便说一下,无论是Jetty、Tomcat还是Reactor Netty,都会以相同的方式调用这个类的stop方法。
在这个stop方法的第二行,我们看到了this.webServer.shutDownGracefully((result) -> callback.run()),根据注入的webServer实例类型,也就是Web服务器的类型来决定接下来的处理内容。这样的实现很符合面向对象的思想,很棒呢。

另外,对此进行进一步调用的是DefaultLifecycleProcessor类。
在这个类中,会逐个调用SmartLifecycle Bean的stop方法。由于这个类在ApplicationContext关闭时被调用,因此最终实现了在Spring Boot关闭时调用Graceful shutdown的效果。

private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
        final CountDownLatch latch, final Set<String> countDownBeanNames) {

    Lifecycle bean = lifecycleBeans.remove(beanName);
    if (bean != null) {
        ...
        try {
            if (bean.isRunning()) {
                if (bean instanceof SmartLifecycle) {
                    ...
                    ((SmartLifecycle) bean).stop(() -> { //ここでSmartLifecycleのstopが順々に呼ばれる
                    ...
                    });
                }
                ...
            }
            ...
        }
        ...
    }
}

总结

这次我看了一下Spring Boot如何实现优雅的关闭。我了解到它是通过Spring的SmartLifecycle机制来实现的。虽然我一直在追踪探究,但其实这个信息在文档中也有明确说明。只是仅仅看文档的话,我完全无法理解SmartLifecycle到底是什么,所以通过深入研究源代码,我才真正理解了SmartLifecycle,觉得这很棒。

以下是中国的本地一种表达方式:

请参考

    • Spring Boot Reference Documentation

 

    • SmartLifecycle (Spring Framework 5.2.7.RELEASE API)

 

    DefaultLifecycleProcessor (Spring Framework 5.2.7.RELEASE API)
bannerAds