当前位置:   article > 正文

Springboot优雅停机-Springboot的shutdown实现_springboot shutdown

springboot shutdown

以下内容基于springboot 2.6.4,jdk-17.0.2版本

导言:

        在很多情况,在应用程序启动后需要关闭的时候,直接对着窗口就是X或者直接KILL -9,这种关闭方式会导致部分正在处理的请求中断,业务停留于不可控业务过程中,可能会引起数据与业务不一致的情况。而正常的做法我们应该使用优雅关机方式,即不再接收新的请求,并将已接受到的请求处理完毕,再关闭程序,释放资源。如Ctrl+C或Kill-2。

Springboot graceful shutdown 应用场景:

        在Springboot中,提供了优雅停机方案,内嵌到Springboot中的4个Web服务器(Jetty,Reactor Netty,Tomcat,Undertow)与反应式和基于servlet的web应用程序,在停止SmartLifecycle的早期阶段会逐步停止应用程序上下文。这个过程中,会给应用程序一个宽限期,然后不再处理新的请求处理,并将已接受到的请求在宽限期内结束,而对触发停机后再接收到的请求处理方式取决于不同的web服务器。Jetty,Reactor Netty,Tomcat将会在网络层停止请求接收,而Undertow将会接收请求,但会直接返回服务器不可用的503状态码。

优雅停机需要Tomcat 9.0.33及以上

使用方式:

开启优雅停机,需要配置server.shutdown属性,如:

  1. server:
  2.   shutdown: “graceful”

还需要配置一个宽限期配置,如:

  1. spring:
  2. lifecycle:
  3. timeout-per-shutdown-phase: “20s”

(以上资料来源于:Spring Boot Reference Documentation v2.6.4 # 8.3 Graceful Shutdown)

原理分析:

SpringBoot在运行创建ApplicationContext时,会通过refreshContext
方法将context注册到shutdown钩子上,再通过SpringApplicationShutdownHook的addRuntimeShutdownHookIfNecessary
方法使用Runtime.getRuntime().addShutdownHook
将SpringApplicationShutdownHook注入到Java的Shutdown中的,建立关系。

详细:

1. SpringBoot启动,SpringApplication类创建ApplicationContext,调用refreshContext方法

  1. public ConfigurableApplicationContext run(String... args) {
  2. long startTime = System.nanoTime();
  3. DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  4. ConfigurableApplicationContext context = null;
  5. configureHeadlessProperty();
  6. SpringApplicationRunListeners listeners = getRunListeners(args);
  7. listeners.starting(bootstrapContext, this.mainApplicationClass);
  8. try {
  9. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  10. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  11. configureIgnoreBeanInfo(environment);
  12. Banner printedBanner = printBanner(environment);
  13. context = createApplicationContext();
  14. context.setApplicationStartup(this.applicationStartup);
  15. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  16. //1.调用refreshContext方法
  17. refreshContext(context);
  18. afterRefresh(context, applicationArguments);
  19. Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
  20. if (this.logStartupInfo) {
  21. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
  22. }
  23. listeners.started(context, timeTakenToStartup);
  24. callRunners(context, applicationArguments);
  25. }
  26. catch (Throwable ex) {
  27. handleRunFailure(context, ex, listeners);
  28. throw new IllegalStateException(ex);
  29. }
  30. try {
  31. Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
  32. listeners.ready(context, timeTakenToReady);
  33. }
  34. catch (Throwable ex) {
  35. handleRunFailure(context, ex, null);
  36. throw new IllegalStateException(ex);
  37. }
  38. return context;
  39. }

2. 在方法中,会先检查是否注册shutdown钩子的开关,若开关打开,则会将context注册到shutdown钩子中,

  1. private void refreshContext(ConfigurableApplicationContext context) {
  2. if (this.registerShutdownHook) {
  3. //注册到钩子
  4. shutdownHook.registerApplicationContext(context);
  5. }
  6. refresh(context);
  7. }

 3. 上一步中,SpringApplication调用的是SpringApplicationShutdownHook的registerApplicationContext方法,在这个方法中,会将其加入到运行时Shutdown钩子中,并开启监听,再将上下文关联到当前contexts中(用于关闭)。

  1. void registerApplicationContext(ConfigurableApplicationContext context) {
  2. //通过Runtime调用
  3. addRuntimeShutdownHookIfNecessary();
  4. synchronized (SpringApplicationShutdownHook.class) {
  5. assertNotInProgress();
  6. //加入监听
  7. context.addApplicationListener(this.contextCloseListener);
  8. //保存上下文
  9. this.contexts.add(context);
  10. }
  11. }

4. 我们重点看addRuntimeShutdownHookIfNecessary方法,这个方法主要是通过Runtime.getRuntime().addShutdownHook将SpringApplicationShutdownHook加入到java的shutdown钩子中。

  1. private final AtomicBoolean shutdownHookAdded = new AtomicBoolean();
  2. private void addRuntimeShutdownHookIfNecessary() {
  3. if (this.shutdownHookAdded.compareAndSet(false, true)) {
  4. addRuntimeShutdownHook();
  5. }
  6. }
  7. void addRuntimeShutdownHook() {
  8. try {
  9. Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));
  10. }
  11. catch (AccessControlException ex) {
  12. // Not allowed in some environments
  13. }
  14. }

几个重点:

        1.这里用的是AtomicBoolean

        2.从这步结束,之前都是Spring的Shutdown钩子处理,之后,都是java中Shutdown钩子处理。

5. 调用运行时的addShutdownHook方法后,会通过ApplicationShutdownHooks.add(hook)方法将钩子添加到java应用程序Shutdown钩子集合中。

  1. public void addShutdownHook(Thread hook) {
  2. @SuppressWarnings("removal")
  3. SecurityManager sm = System.getSecurityManager();
  4. if (sm != null) {
  5. sm.checkPermission(new RuntimePermission("shutdownHooks"));
  6. }
  7. ApplicationShutdownHooks.add(hook);
  8. }

6. 在将ApplicationShutdownHooks添加到钩子集合中前,会检查钩子的状态,确认状态无误后,再将钩子加入到集合中。加入集合后,流程结束。

  1. static synchronized void add(Thread hook) {
  2. if(hooks == null)
  3. throw new IllegalStateException("Shutdown in progress");
  4. if (hook.isAlive())
  5. throw new IllegalArgumentException("Hook already running");
  6. if (hooks.containsKey(hook))
  7. throw new IllegalArgumentException("Hook previously registered");
  8. hooks.put(hook, hook);
  9. }

7. 在ApplicationShutdownHooks有个静态方法会调用Shutdown初始化钩子集合。

  1. private static IdentityHashMap<Thread, Thread> hooks;
  2. static {
  3. try {
  4. Shutdown.add(1 /* shutdown hook invocation order */,
  5. false /* not registered if shutdown in progress */,
  6. new Runnable() {
  7. public void run() {
  8. runHooks();
  9. }
  10. }
  11. );
  12. hooks = new IdentityHashMap<>();
  13. } catch (IllegalStateException e) {
  14. // application shutdown hooks cannot be added if
  15. // shutdown is in progress.
  16. hooks = null;
  17. }
  18. }

这里有个地方有点意思:这里的钩子用的是IdentityHashMap存储,意味着能将两个‘相同’的钩子加入到钩子集合中。

这章说了Springboot的Shutdown实现,下一章再将Java的Shutdown详细说说。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/334248
推荐阅读
相关标签
  

闽ICP备14008679号