我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spring Boot 优雅停机原理详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spring Boot 优雅停机原理详解

正文

SpringBoot 从2.3.0.RELEASE 开始支持 web 服务器的优雅停机

看看官方文档是怎么介绍这一新特性的

“ 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)。

如何开启优雅停机

server:
  # 设置关闭方式为优雅关闭
  shutdown: graceful
spring:
  lifecycle:
    # 优雅关闭超时时间, 默认30s
    timeout-per-shutdown-phase: 30s

优雅停机原理

shutdown hook

在 Java 程序中可以通过添加钩子,在程序退出时会执行钩子方法,从而实现关闭资源、平滑退出等功能。

public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行 ShutdownHook ...");
            }
        }));
    }

覆盖以下场景:

  • 代码主动关闭:如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

private void refreshContext(ConfigurableApplicationContext context) {
   // 默认为true
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
   refresh((ApplicationContext) context);
}

org.springframework.context.support.AbstractApplicationContext

@Override
public void registerShutdownHook() {
   if (this.shutdownHook == null) {
      // No shutdown hook registered yet.
      this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
         @Override
         public void run() {
            synchronized (startupShutdownMonitor) {
               // 回调去关闭容器
               doClose();
            }
         }
      };
      // 注册钩子
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
   }
}

注册实现smartLifecycle的Bean

在创建 webserver 的时候,会创建一个实现smartLifecycle的 bean,用来支撑 server 的优雅关闭。

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext

private void createWebServer() {
      // 省略其他无关代码
      this.webServer = factory.getWebServer(getSelfInitializer());
      // 注册webServerGracefulShutdown用来实现server优雅关闭
      getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));
      // 省略其他无关代码
}

可以看到 WebServerGracefulShutdownLifecycle 类实现SmartLifecycle接口,重写了 stop 方法,stop 方法会触发 webserver 的优雅关闭方法(取决于具体使用的 webserver 如 tomcatWebServer)。

org.springframework.boot.web.servlet.context.WebServerGracefulShutdownLifecycle

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
   @Override
   public void stop(Runnable callback) {
      this.running = false;
      // 优雅关闭server
      this.webServer.shutDownGracefully((result) -> callback.run());
   }
}

org.springframework.boot.web.embedded.tomcat.TomcatWebServer

public class TomcatWebServer implements WebServer {
   public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
      this.tomcat = tomcat;
      this.autoStart = autoStart;
      // 如果SpringBoot开启了优雅停机配置,shutdown = Shutdown.GRACEFUL
      this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
      initialize();
   }
   @Override
   public void shutDownGracefully(GracefulShutdownCallback callback) {
      if (this.gracefulShutdown == null) {
         // 如果没有开启优雅停机,会立即关闭tomcat服务器
         callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
         return;
      }
      // 优雅关闭服务器
      this.gracefulShutdown.shutDownGracefully(callback);
   }
}

smartLifecycle的工作原理

上文提到钩子方法被调用后会执行 doColse()方法,在关闭容器之前,会通过 lifecycleProcessor 调用 lifecycle 的方法。

org.springframework.context.support.AbstractApplicationContext

protected void doClose() {
   if (this.active.get() && this.closed.compareAndSet(false, true)) {
      LiveBeansView.unregisterApplicationContext(this);
      // 发布 ContextClosedEvent 事件
      publishEvent(new ContextClosedEvent(this));
      // 回调所有实现Lifecycle 接口的Bean的stop方法
      if (this.lifecycleProcessor != null) {
            this.lifecycleProcessor.onClose();
      }
      // 销毁bean, 关闭容器
      destroyBeans();
      closeBeanFactory();
      onClose();
      if (this.earlyApplicationListeners != null) {
         this.applicationListeners.clear();
         this.applicationListeners.addAll(this.earlyApplicationListeners);
      }
      // Switch to inactive.
      this.active.set(false);
   }
}

关闭 Lifecycle Bean 的入口: org.springframework.context.support.DefaultLifecycleProcessor

public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {
   @Override
   public void onClose() {
      stopBeans();
      this.running = false;
   }
   private void stopBeans() {
      //获取所有的 Lifecycle bean
      Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
      //按Phase值对bean分组, 如果没有实现 Phased 接口则认为 Phase 是 0
      Map<Integer, LifecycleGroup> phases = new HashMap<>();
      lifecycleBeans.forEach((beanName, bean) -> {
         int shutdownPhase = getPhase(bean);
         LifecycleGroup group = phases.get(shutdownPhase);
         if (group == null) {
            group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
            phases.put(shutdownPhase, group);
         }
         group.add(beanName, bean);
      });
      if (!phases.isEmpty()) {
         List<Integer> keys = new ArrayList<>(phases.keySet());
         //按照 Phase 值倒序
         keys.sort(Collections.reverseOrder());
         // Phase值越大优先级越高,先执行
         for (Integer key : keys) {
            phases.get(key).stop();
         }
      }
   }

DefaultLifecycleProcessor 的 stop 方法执行流程:

  • 获取容器中的所有实现了 Lifecycle 接口的 Bean。(smartLifecycle 接口继承了 Lifecycle)
  • 再对包含所有 bean 的 List 分组按 phase 值倒序排序,值大的排前面。 (没有实现 Phased 接口, Phase 默认为0)
  • 依次调用各分组的里 bean 的 stop 方法 ( Phase 越大 stop 方法优先执行)

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

从上文我们已经可以梳理出,优雅停机的执行流程,下面可以看下停机超时时间是如何控制的。

org.springframework.context.support.DefaultLifecycleProcessor

// DefaultLifecycleProcessor内部类
private class LifecycleGroup {
   public void stop() {
      this.members.sort(Collections.reverseOrder());
      // count值默认为该组smartLifeCycel bean的数量
      CountDownLatch latch = new CountDownLatch(this.smartMemberCount);
      // 用于日志打印,打印等待超时未关闭成功的beanName
      Set<String> countDownBeanNames = Collections.synchronizedSet(new LinkedHashSet<>());
      Set<String> lifecycleBeanNames = new HashSet<>(this.lifecycleBeans.keySet());
      for (LifecycleGroupMember member : this.members) {
         if (lifecycleBeanNames.contains(member.name)) {
            // bean如果还没关闭,执行关闭方法
            doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
         }
         else if (member.bean instanceof SmartLifecycle) {
            // 如果是SmartLifecycle bean 并且已经被提前处理了(依赖其他更优先关闭的bean,会提前关闭)
            latch.countDown();
         }
      }
      try {
         // 等待该组 所有smartLifeCycel bean成功关闭 或者 超时
         // 等待时间默认30s, 如果没有配置timeout-per-shutdown-phase
         latch.await(this.timeout, TimeUnit.MILLISECONDS);
      }
      catch (InterruptedException ex) {
         Thread.currentThread().interrupt();
      }
   }
}
private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
      final CountDownLatch latch, final Set<String> countDownBeanNames) {
   // 从未关闭的bean List中移除
   Lifecycle bean = lifecycleBeans.remove(beanName);
   if (bean != null) {
      String[] dependentBeans = getBeanFactory().getDependentBeans(beanName);
      // 如果该bean被其他bean依赖,优先关闭那些bean
      for (String dependentBean : dependentBeans) {
         doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
      }
      // Lifecycel#isRunning 需要为true才会执行stop方法
      if (bean.isRunning()) {
           if (bean instanceof SmartLifecycle) {
              // 关闭之前先记录,如果超时没关闭成功 用于打印日志提醒
              countDownBeanNames.add(beanName);
              ((SmartLifecycle) bean).stop(() -> {
                 // 执行成功countDown
                 latch.countDown();
                 // 关闭成功移除
                 countDownBeanNames.remove(beanName);
              });
           }
           else {
               // 普通Lifecycle bean直接调用stop方法
               bean.stop();
           }
       }
       else if (bean instanceof SmartLifecycle) {
            // 如何SmartLifecycle不需要关闭,直接countDown
           latch.countDown();
       }
   }
}
  • 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 的关闭。

以上就是Spring Boot 优雅停机原理详解的详细内容,更多关于Spring Boot 停机原理的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Spring Boot 优雅停机原理详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring Boot 优雅停机原理详解

这篇文章主要为大家介绍了Spring Boot 优雅停机原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-16

Java优雅停机的实现及原理是什么

这篇文章给大家介绍Java优雅停机的实现及原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊!其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录