欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Boot?優(yōu)雅停機原理詳解

 更新時間:2023年02月16日 09:23:22   作者:政采云技術團隊  
這篇文章主要為大家介紹了Spring?Boot?優(yōu)雅停機原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

SpringBoot 從2.3.0.RELEASE 開始支持 web 服務器的優(yōu)雅停機

看看官方文檔是怎么介紹這一新特性的

“ 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."

四種內(nèi)嵌 web 服務器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及 reactive 和基于 servlet 的 web 應用程序都支持優(yōu)雅停機,它作為關閉應用程序上下文的一部分發(fā)生,并且是SmartLifecyclebean里最早進行關閉的。此停止處理會有個超時機制,該超時提供了一個寬限期,在此期間允許完成現(xiàn)有請求,但不允許新請求。具體實現(xiàn)取決于所使用的web服務器。Jetty、Reactor Netty 和 Tomcat 將停止接受網(wǎng)絡層的請求。Undertow 將接受請求,但立即響應服務不可用(503)。

如何開啟優(yōu)雅停機

server:
  # 設置關閉方式為優(yōu)雅關閉
  shutdown: graceful
spring:
  lifecycle:
    # 優(yōu)雅關閉超時時間, 默認30s
    timeout-per-shutdown-phase: 30s

優(yōu)雅停機原理

shutdown hook

在 Java 程序中可以通過添加鉤子,在程序退出時會執(zhí)行鉤子方法,從而實現(xiàn)關閉資源、平滑退出等功能。

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

覆蓋以下場景:

  • 代碼主動關閉:如System.exit()
  • 捕獲kill信號: kill -1(HUP), kill - 2(INT), kill -15(TERM)
  • 用戶線程結(jié)束: 會在最后一個非守護線程結(jié)束時被 JNI 的DestroyJavaVM方法調(diào)用

說明: kill -9 會直接殺死進程不會觸發(fā) shutdownhook 方法執(zhí)行,shutdownhook 回調(diào)方法會啟動新線程,注冊多個鉤子會并發(fā)執(zhí)行。

SpringBoot注冊 Shutdown Hook

SpringBoot 在啟動過程中,則會默認注冊一個 Shutdown Hook,在應用被關閉的時候,會觸發(fā)鉤子調(diào)用 doClose()方法,去關閉容器。(也可以通過 actuate 來優(yōu)雅關閉應用,不在本文討論范圍)

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) {
               // 回調(diào)去關閉容器
               doClose();
            }
         }
      };
      // 注冊鉤子
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
   }
}

注冊實現(xiàn)smartLifecycle的Bean

在創(chuàng)建 webserver 的時候,會創(chuàng)建一個實現(xiàn)smartLifecycle的 bean,用來支撐 server 的優(yōu)雅關閉。

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

private void createWebServer() {
      // 省略其他無關代碼
      this.webServer = factory.getWebServer(getSelfInitializer());
      // 注冊webServerGracefulShutdown用來實現(xiàn)server優(yōu)雅關閉
      getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));
      // 省略其他無關代碼
}

可以看到 WebServerGracefulShutdownLifecycle 類實現(xiàn)SmartLifecycle接口,重寫了 stop 方法,stop 方法會觸發(fā) webserver 的優(yōu)雅關閉方法(取決于具體使用的 webserver 如 tomcatWebServer)。

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

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
   @Override
   public void stop(Runnable callback) {
      this.running = false;
      // 優(yōu)雅關閉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開啟了優(yōu)雅停機配置,shutdown = Shutdown.GRACEFUL
      this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
      initialize();
   }
   @Override
   public void shutDownGracefully(GracefulShutdownCallback callback) {
      if (this.gracefulShutdown == null) {
         // 如果沒有開啟優(yōu)雅停機,會立即關閉tomcat服務器
         callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
         return;
      }
      // 優(yōu)雅關閉服務器
      this.gracefulShutdown.shutDownGracefully(callback);
   }
}

smartLifecycle的工作原理

上文提到鉤子方法被調(diào)用后會執(zhí)行 doColse()方法,在關閉容器之前,會通過 lifecycleProcessor 調(diào)用 lifecycle 的方法。

org.springframework.context.support.AbstractApplicationContext

protected void doClose() {
   if (this.active.get() && this.closed.compareAndSet(false, true)) {
      LiveBeansView.unregisterApplicationContext(this);
      // 發(fā)布 ContextClosedEvent 事件
      publishEvent(new ContextClosedEvent(this));
      // 回調(diào)所有實現(xiàn)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分組, 如果沒有實現(xiàn) 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值越大優(yōu)先級越高,先執(zhí)行
         for (Integer key : keys) {
            phases.get(key).stop();
         }
      }
   }

DefaultLifecycleProcessor 的 stop 方法執(zhí)行流程:

  • 獲取容器中的所有實現(xiàn)了 Lifecycle 接口的 Bean。(smartLifecycle 接口繼承了 Lifecycle)
  • 再對包含所有 bean 的 List 分組按 phase 值倒序排序,值大的排前面。 (沒有實現(xiàn) Phased 接口, Phase 默認為0)
  • 依次調(diào)用各分組的里 bean 的 stop 方法 ( Phase 越大 stop 方法優(yōu)先執(zhí)行)

優(yōu)雅停機超時時間如何控制

從上文我們已經(jīng)可以梳理出,優(yōu)雅停機的執(zhí)行流程,下面可以看下停機超時時間是如何控制的。

org.springframework.context.support.DefaultLifecycleProcessor

// DefaultLifecycleProcessor內(nèi)部類
private class LifecycleGroup {
   public void stop() {
      this.members.sort(Collections.reverseOrder());
      // count值默認為該組smartLifeCycel bean的數(shù)量
      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如果還沒關閉,執(zhí)行關閉方法
            doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
         }
         else if (member.bean instanceof SmartLifecycle) {
            // 如果是SmartLifecycle bean 并且已經(jīng)被提前處理了(依賴其他更優(yōu)先關閉的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依賴,優(yōu)先關閉那些bean
      for (String dependentBean : dependentBeans) {
         doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
      }
      // Lifecycel#isRunning 需要為true才會執(zhí)行stop方法
      if (bean.isRunning()) {
           if (bean instanceof SmartLifecycle) {
              // 關閉之前先記錄,如果超時沒關閉成功 用于打印日志提醒
              countDownBeanNames.add(beanName);
              ((SmartLifecycle) bean).stop(() -> {
                 // 執(zhí)行成功countDown
                 latch.countDown();
                 // 關閉成功移除
                 countDownBeanNames.remove(beanName);
              });
           }
           else {
               // 普通Lifecycle bean直接調(diào)用stop方法
               bean.stop();
           }
       }
       else if (bean instanceof SmartLifecycle) {
            // 如何SmartLifecycle不需要關閉,直接countDown
           latch.countDown();
       }
   }
}
  • DefaultLifecycleProcessor 利用 CountDownLatch 來控制等待bean的關閉方法執(zhí)行完畢,count=本組 SmartLifecycle bean 的數(shù)量,只有所有 SmartLifecycle 都執(zhí)行完,回調(diào)執(zhí)行 latch.countDown(),主線程才會結(jié)束等待,否則直到超時。
  • timeout-per-shutdown-phase: 30s, 該配置是針對每一組 Lifecycle bean 分別生效,不是所有的 Lifecycle bean,比如有2組不同puase 值的 bean, 會分別有最長 30s 等待時間。
  • 超時等待只對異步執(zhí)行 SmartLifecycle #stop(Runnable callback) 方法有效果,同步執(zhí)行沒有效果。
  • 如果不同組的 Lifecycle bean 之間有依賴關系,當前組 bean 被其他組的 bean 依賴,其他組的 bean 會先進行關閉(也會調(diào)用本輪生成 latch 對象的 countDown()),導致本輪的 latch.countDown()調(diào)用次數(shù)會超過初始化的 count 值,導致提前結(jié)束等待的情況發(fā)生。

優(yōu)雅停機的執(zhí)行流程總結(jié):

  • SpringBoot 通過 Shutdown Hook 來注冊 doclose() 回調(diào)方法,在應用關閉的時候觸發(fā)執(zhí)行。
  • SpringBoot 在創(chuàng)建 webserver的時候,會注冊實現(xiàn) smartLifecycel 接口的 bean,用來優(yōu)雅關閉 tomcat
  • doClose()在銷毀 bean, 關閉容器之前會執(zhí)行所有實現(xiàn) Lifecycel 接口 bean 的 stop方法,并且會按 Phase 值分組, phase 大的優(yōu)先執(zhí)行。
  • WebServerGracefulShutdownLifecycle,Phase=Inter.MAX_VALUE,處于最優(yōu)先執(zhí)行序列,所以 tomcat 會先觸發(fā)優(yōu)雅關閉,并且tomcat 關閉方法是異步執(zhí)行的,主線會繼續(xù)調(diào)用執(zhí)行本組其他 bean 的關閉方法,然后等待所有 bean 關閉完畢,超過等待時間,會執(zhí)行下一組 Lifecycle bean 的關閉。

以上就是Spring Boot 優(yōu)雅停機原理詳解的詳細內(nèi)容,更多關于Spring Boot 停機原理的資料請關注腳本之家其它相關文章!

相關文章

  • Java中實體類為什么要實現(xiàn)Serializable序列化的作用

    Java中實體類為什么要實現(xiàn)Serializable序列化的作用

    這篇文章主要介紹了Java中實體類為什么要實現(xiàn)Serializable序列化的作用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • SpringBoot JPA出現(xiàn)錯誤:No identifier specified for en解決方案

    SpringBoot JPA出現(xiàn)錯誤:No identifier specified&nb

    這篇文章主要介紹了SpringBoot JPA出現(xiàn)錯誤:No identifier specified for en解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java網(wǎng)上圖書商城(9)支付模塊

    java網(wǎng)上圖書商城(9)支付模塊

    這篇文章主要為大家詳細介紹了java網(wǎng)上圖書商城,支付模塊,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • Kafka中Producer和Consumer的作用詳解

    Kafka中Producer和Consumer的作用詳解

    這篇文章主要介紹了Kafka中Producer和Consumer的作用詳解,Kafka是一個分布式的流處理平臺,它的核心是消息系統(tǒng),Producer是Kafka中用來將消息發(fā)送到Broker的組件之一,它將消息發(fā)布到主題,并且負責按照指定的分區(qū)策略將消息分配到對應的分區(qū)中,需要的朋友可以參考下
    2023-12-12
  • 詳解使用MyBatis Generator自動創(chuàng)建代碼

    詳解使用MyBatis Generator自動創(chuàng)建代碼

    這篇文章主要介紹了使用MyBatis Generator自動創(chuàng)建代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • logback如何自定義日志存儲

    logback如何自定義日志存儲

    這篇文章主要介紹了logback如何自定義日志存儲的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java線程池復用線程的秘密你知道嗎

    Java線程池復用線程的秘密你知道嗎

    這篇文章主要為大家詳細介紹了Java線程池復用線程的秘密,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望您能夠多多關注

    2022-03-03
  • 淺談Maven resrouce下filtering作用

    淺談Maven resrouce下filtering作用

    Filtering是Maven Resources Plugin的一個功能,本文主要介紹了淺談Maven resrouce下filtering作用,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Mybatis Plus 大數(shù)據(jù)游標分頁的實現(xiàn)

    Mybatis Plus 大數(shù)據(jù)游標分頁的實現(xiàn)

    使用MyBatis Plus的游標分頁,我們可以輕松應對大數(shù)據(jù)量的場景,本文主要介紹了Mybatis Plus 大數(shù)據(jù)游標分頁的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • SpringMVC中的Model對象用法說明

    SpringMVC中的Model對象用法說明

    這篇文章主要介紹了SpringMVC中的Model對象用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06

最新評論