看了一下源码,发现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服务器对于“不接受新请求”这一行为会有不同的行为。
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)