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

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

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

正文

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

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

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

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

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

優(yōu)雅停機(jī)原理

shutdown hook

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

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

覆蓋以下場景:

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

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

SpringBoot注冊 Shutdown Hook

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

org.springframework.boot.SpringApplication#refreshContext

private void refreshContext(ConfigurableApplicationContext context) {
   // 默認(rèn)為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)去關(guān)閉容器
               doClose();
            }
         }
      };
      // 注冊鉤子
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
   }
}

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

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

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

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

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

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

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
   @Override
   public void stop(Runnable callback) {
      this.running = false;
      // 優(yōu)雅關(guān)閉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)雅停機(jī)配置,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)雅停機(jī),會立即關(guān)閉tomcat服務(wù)器
         callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
         return;
      }
      // 優(yōu)雅關(guān)閉服務(wù)器
      this.gracefulShutdown.shutDownGracefully(callback);
   }
}

smartLifecycle的工作原理

上文提到鉤子方法被調(diào)用后會執(zhí)行 doColse()方法,在關(guān)閉容器之前,會通過 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, 關(guān)閉容器
      destroyBeans();
      closeBeanFactory();
      onClose();
      if (this.earlyApplicationListeners != null) {
         this.applicationListeners.clear();
         this.applicationListeners.addAll(this.earlyApplicationListeners);
      }
      // Switch to inactive.
      this.active.set(false);
   }
}

關(guān)閉 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 接口則認(rèn)為 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 默認(rèn)為0)
  • 依次調(diào)用各分組的里 bean 的 stop 方法 ( Phase 越大 stop 方法優(yōu)先執(zhí)行)

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

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

org.springframework.context.support.DefaultLifecycleProcessor

// DefaultLifecycleProcessor內(nèi)部類
private class LifecycleGroup {
   public void stop() {
      this.members.sort(Collections.reverseOrder());
      // count值默認(rèn)為該組smartLifeCycel bean的數(shù)量
      CountDownLatch latch = new CountDownLatch(this.smartMemberCount);
      // 用于日志打印,打印等待超時未關(guān)閉成功的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如果還沒關(guān)閉,執(zhí)行關(guān)閉方法
            doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
         }
         else if (member.bean instanceof SmartLifecycle) {
            // 如果是SmartLifecycle bean 并且已經(jīng)被提前處理了(依賴其他更優(yōu)先關(guān)閉的bean,會提前關(guān)閉)
            latch.countDown();
         }
      }
      try {
         // 等待該組 所有smartLifeCycel bean成功關(guān)閉 或者 超時
         // 等待時間默認(rèn)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) {
   // 從未關(guān)閉的bean List中移除
   Lifecycle bean = lifecycleBeans.remove(beanName);
   if (bean != null) {
      String[] dependentBeans = getBeanFactory().getDependentBeans(beanName);
      // 如果該bean被其他bean依賴,優(yōu)先關(guān)閉那些bean
      for (String dependentBean : dependentBeans) {
         doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
      }
      // Lifecycel#isRunning 需要為true才會執(zhí)行stop方法
      if (bean.isRunning()) {
           if (bean instanceof SmartLifecycle) {
              // 關(guān)閉之前先記錄,如果超時沒關(guān)閉成功 用于打印日志提醒
              countDownBeanNames.add(beanName);
              ((SmartLifecycle) bean).stop(() -> {
                 // 執(zhí)行成功countDown
                 latch.countDown();
                 // 關(guān)閉成功移除
                 countDownBeanNames.remove(beanName);
              });
           }
           else {
               // 普通Lifecycle bean直接調(diào)用stop方法
               bean.stop();
           }
       }
       else if (bean instanceof SmartLifecycle) {
            // 如何SmartLifecycle不需要關(guān)閉,直接countDown
           latch.countDown();
       }
   }
}
  • DefaultLifecycleProcessor 利用 CountDownLatch 來控制等待bean的關(guān)閉方法執(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 之間有依賴關(guān)系,當(dāng)前組 bean 被其他組的 bean 依賴,其他組的 bean 會先進(jìn)行關(guān)閉(也會調(diào)用本輪生成 latch 對象的 countDown()),導(dǎo)致本輪的 latch.countDown()調(diào)用次數(shù)會超過初始化的 count 值,導(dǎo)致提前結(jié)束等待的情況發(fā)生。

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

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

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

相關(guān)文章

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

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

    這篇文章主要介紹了Java中實體類為什么要實現(xiàn)Serializable序列化的作用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    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)支付模塊

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

    Kafka中Producer和Consumer的作用詳解

    這篇文章主要介紹了Kafka中Producer和Consumer的作用詳解,Kafka是一個分布式的流處理平臺,它的核心是消息系統(tǒng),Producer是Kafka中用來將消息發(fā)送到Broker的組件之一,它將消息發(fā)布到主題,并且負(fù)責(zé)按照指定的分區(qū)策略將消息分配到對應(yīng)的分區(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線程池復(fù)用線程的秘密你知道嗎

    Java線程池復(fù)用線程的秘密你知道嗎

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

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

    淺談Maven resrouce下filtering作用

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

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

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

    SpringMVC中的Model對象用法說明

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

最新評論