优雅停机

SpringBoot 从2.3.0.RELEASE 开始支持 web 服务器的优雅停机
notion image
看看官方文档是怎么介绍这一新特性的
“ Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. It occurs as part of closing the application context and is performed in the earliest phase of stopping SmartLifecycle beans. This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted. The exact way in which new requests are not permitted varies depending on the web server that is being used. Jetty, Reactor Netty, and Tomcat will stop accepting requests at the network layer. Undertow will accept requests but respond immediately with a service unavailable (503) response."
四种内嵌 web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及 reactive 和基于 servlet 的 web 应用程序都支持优雅停机,它作为关闭应用程序上下文的一部分发生,并且是SmartLifecyclebean里最早进行关闭的。此停止处理会有个超时机制,该超时提供了一个宽限期,在此期间允许完成现有请求,但不允许新请求。具体实现取决于所使用的web服务器。Jetty、Reactor Netty 和 Tomcat 将停止接受网络层的请求。Undertow 将接受请求,但立即响应服务不可用(503)。

如何开启优雅停机

优雅停机原理

shutdown hook

在 Java 程序中可以通过添加钩子,在程序退出时会执行钩子方法,从而实现关闭资源、平滑退出等功能。
notion image
覆盖以下场景:
  • 代码主动关闭:如System.exit()
  • 捕获kill信号: kill -1(HUP), kill - 2(INT), kill -15(TERM)
  • 用户线程结束: 会在最后一个非守护线程结束时被 JNI 的DestroyJavaVM方法调用
说明: kill -9 会直接杀死进程不会触发 shutdownhook 方法执行,shutdownhook 回调方法会启动新线程,注册多个钩子会并发执行。

SpringBoot注册 Shutdown Hook

SpringBoot 在启动过程中,则会默认注册一个 Shutdown Hook,在应用被关闭的时候,会触发钩子调用 doClose()方法,去关闭容器。(也可以通过 actuate 来优雅关闭应用,不在本文讨论范围)
org.springframework.boot.SpringApplication#refreshContext
org.springframework.context.support.AbstractApplicationContext

注册实现smartLifecycle的Bean

在创建 webserver 的时候,会创建一个实现smartLifecycle的 bean,用来支撑 server 的优雅关闭。
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
可以看到 WebServerGracefulShutdownLifecycle 类实现SmartLifecycle接口,重写了 stop 方法,stop 方法会触发 webserver 的优雅关闭方法(取决于具体使用的 webserver 如 tomcatWebServer)。
org.springframework.boot.web.servlet.context.WebServerGracefulShutdownLifecycle
org.springframework.boot.web.embedded.tomcat.TomcatWebServer

smartLifecycle的工作原理

上文提到钩子方法被调用后会执行 doColse()方法,在关闭容器之前,会通过 lifecycleProcessor 调用 lifecycle 的方法。
org.springframework.context.support.AbstractApplicationContext
关闭 Lifecycle Bean 的入口: org.springframework.context.support.DefaultLifecycleProcessor
DefaultLifecycleProcessor 的 stop 方法执行流程:
  • 获取容器中的所有实现了 Lifecycle 接口的 Bean。(smartLifecycle 接口继承了 Lifecycle)
  • 再对包含所有 bean 的 List 分组按 phase 值倒序排序,值大的排前面。 (没有实现 Phased 接口, Phase 默认为0)
  • 依次调用各分组的里 bean 的 stop 方法 ( Phase 越大 stop 方法优先执行)

优雅停机超时时间如何控制

从上文我们已经可以梳理出,优雅停机的执行流程,下面可以看下停机超时时间是如何控制的。
org.springframework.context.support.DefaultLifecycleProcessor
  • DefaultLifecycleProcessor 利用 CountDownLatch 来控制等待bean的关闭方法执行完毕,count=本组 SmartLifecycle bean 的数量,只有所有 SmartLifecycle 都执行完,回调执行 latch.countDown(),主线程才会结束等待,否则直到超时。
  • timeout-per-shutdown-phase: 30s, 该配置是针对每一组 Lifecycle bean 分别生效,不是所有的 Lifecycle bean,比如有2组不同puase 值的 bean, 会分别有最长 30s 等待时间。
  • 超时等待只对异步执行 SmartLifecycle #stop(Runnable callback) 方法有效果,同步执行没有效果。
  • 如果不同组的 Lifecycle bean 之间有依赖关系,当前组 bean 被其他组的 bean 依赖,其他组的 bean 会先进行关闭(也会调用本轮生成 latch 对象的 countDown()),导致本轮的 latch.countDown()调用次数会超过初始化的 count 值,导致提前结束等待的情况发生。

优雅停机的执行流程总结:

  • SpringBoot 通过 Shutdown Hook 来注册 doclose() 回调方法,在应用关闭的时候触发执行。
  • SpringBoot 在创建 webserver的时候,会注册实现 smartLifecycel 接口的 bean,用来优雅关闭 tomcat
  • doClose()在销毁 bean, 关闭容器之前会执行所有实现 Lifecycel 接口 bean 的 stop方法,并且会按 Phase 值分组, phase 大的优先执行。
  • WebServerGracefulShutdownLifecycle,Phase=Inter.MAX_VALUE,处于最优先执行序列,所以 tomcat 会先触发优雅关闭,并且tomcat 关闭方法是异步执行的,主线会继续调用执行本组其他 bean 的关闭方法,然后等待所有 bean 关闭完毕,超过等待时间,会执行下一组 Lifecycle bean 的关闭。
Loading...
目录
文章列表
王小扬博客
产品
Think
Git
软件开发
计算机网络
CI
DB
设计
缓存
Docker
Node
操作系统
Java
大前端
Nestjs
其他
PHP