亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時(shí)間:2023年02月16日 09:23:22   作者:政采云技術(shù)團(tuán)隊(duì)  
這篇文章主要為大家介紹了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)閉的。此停止處理會(huì)有個(gè)超時(shí)機(jī)制,該超時(shí)提供了一個(gè)寬限期,在此期間允許完成現(xiàn)有請(qǐng)求,但不允許新請(qǐng)求。具體實(shí)現(xiàn)取決于所使用的web服務(wù)器。Jetty、Reactor Netty 和 Tomcat 將停止接受網(wǎng)絡(luò)層的請(qǐng)求。Undertow 將接受請(qǐng)求,但立即響應(yīng)服務(wù)不可用(503)。

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

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

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

shutdown hook

在 Java 程序中可以通過添加鉤子,在程序退出時(shí)會(huì)執(zhí)行鉤子方法,從而實(shí)現(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 ...");
            }
        }));
    }

覆蓋以下場(chǎng)景:

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

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

SpringBoot注冊(cè) Shutdown Hook

SpringBoot 在啟動(dòng)過程中,則會(huì)默認(rèn)注冊(cè)一個(gè) Shutdown Hook,在應(yīng)用被關(guān)閉的時(shí)候,會(huì)觸發(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();
            }
         }
      };
      // 注冊(cè)鉤子
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
   }
}

注冊(cè)實(shí)現(xiàn)smartLifecycle的Bean

在創(chuàng)建 webserver 的時(shí)候,會(huì)創(chuàng)建一個(gè)實(shí)現(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());
      // 注冊(cè)webServerGracefulShutdown用來實(shí)現(xiàn)server優(yōu)雅關(guān)閉
      getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));
      // 省略其他無關(guān)代碼
}

可以看到 WebServerGracefulShutdownLifecycle 類實(shí)現(xiàn)SmartLifecycle接口,重寫了 stop 方法,stop 方法會(huì)觸發(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ī),會(huì)立即關(guān)閉tomcat服務(wù)器
         callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
         return;
      }
      // 優(yōu)雅關(guān)閉服務(wù)器
      this.gracefulShutdown.shutDownGracefully(callback);
   }
}

smartLifecycle的工作原理

上文提到鉤子方法被調(diào)用后會(huì)執(zhí)行 doColse()方法,在關(guān)閉容器之前,會(huì)通過 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)所有實(shí)現(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值對(duì)bean分組, 如果沒有實(shí)現(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)先級(jí)越高,先執(zhí)行
         for (Integer key : keys) {
            phases.get(key).stop();
         }
      }
   }

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

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

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

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

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);
      // 用于日志打印,打印等待超時(shí)未關(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,會(huì)提前關(guān)閉)
            latch.countDown();
         }
      }
      try {
         // 等待該組 所有smartLifeCycel bean成功關(guān)閉 或者 超時(shí)
         // 等待時(shí)間默認(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才會(huì)執(zhí)行stop方法
      if (bean.isRunning()) {
           if (bean instanceof SmartLifecycle) {
              // 關(guān)閉之前先記錄,如果超時(shí)沒關(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(),主線程才會(huì)結(jié)束等待,否則直到超時(shí)。
  • timeout-per-shutdown-phase: 30s, 該配置是針對(duì)每一組 Lifecycle bean 分別生效,不是所有的 Lifecycle bean,比如有2組不同puase 值的 bean, 會(huì)分別有最長(zhǎng) 30s 等待時(shí)間。
  • 超時(shí)等待只對(duì)異步執(zhí)行 SmartLifecycle #stop(Runnable callback) 方法有效果,同步執(zhí)行沒有效果。
  • 如果不同組的 Lifecycle bean 之間有依賴關(guān)系,當(dāng)前組 bean 被其他組的 bean 依賴,其他組的 bean 會(huì)先進(jìn)行關(guān)閉(也會(huì)調(diào)用本輪生成 latch 對(duì)象的 countDown()),導(dǎo)致本輪的 latch.countDown()調(diào)用次數(shù)會(huì)超過初始化的 count 值,導(dǎo)致提前結(jié)束等待的情況發(fā)生。

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

    Kafka中Producer和Consumer的作用詳解

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

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

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

    logback如何自定義日志存儲(chǔ)

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

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

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

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

    淺談Maven resrouce下filtering作用

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

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

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

    SpringMVC中的Model對(duì)象用法說明

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

最新評(píng)論