最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

正文

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

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

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

優(yōu)雅停機原理

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é)束: 會在最后一個非守護線程結(jié)束時被 JNI 的DestroyJavaVM方法調(diào)用

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

SpringBoot注冊 Shutdown Hook

SpringBoot 在啟動過程中,則會默認注冊一個 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) {
   // 默認為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)雅停機配置,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)雅停機,會立即關(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 接口則認為 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);
      // 用于日志打印,打印等待超時未關(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)閉 或者 超時
         // 等待時間默認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 會先進行關(guān)閉(也會調(diào)用本輪生成 latch 對象的 countDown()),導(dǎo)致本輪的 latch.countDown()調(diào)用次數(shù)會超過初始化的 count 值,導(dǎo)致提前結(jié)束等待的情況發(fā)生。

優(yōu)雅停機的執(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)雅停機原理詳解的詳細內(nèi)容,更多關(guān)于Spring Boot 停機原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java排序算法中的快速排序算法實現(xiàn)

    Java排序算法中的快速排序算法實現(xiàn)

    這篇文章主要介紹了Java排序算法中的快速排序算法實現(xiàn),通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對這兩部分數(shù)據(jù)分別進行快速排序,需要的朋友可以參考下
    2023-12-12
  • Kafka的安裝及接入SpringBoot的詳細過程

    Kafka的安裝及接入SpringBoot的詳細過程

    Kafka 是一種高性能、分布式的消息隊列系統(tǒng),最初由 LinkedIn 公司開發(fā),并于2011年成為 Apache 頂級項目,這篇文章主要介紹了Kafka的安裝及接入SpringBoot,需要的朋友可以參考下
    2024-05-05
  • jedis的testWhileIdle用法源碼解讀

    jedis的testWhileIdle用法源碼解讀

    這篇文章主要為大家介紹了jedis的testWhileIdle用法源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • Eclipse插件開發(fā)實現(xiàn)控制臺輸出信息的方法

    Eclipse插件開發(fā)實現(xiàn)控制臺輸出信息的方法

    今天小編就為大家分享一篇關(guān)于Eclipse插件開發(fā)實現(xiàn)控制臺輸出信息的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • spring security中的csrf防御原理(跨域請求偽造)

    spring security中的csrf防御原理(跨域請求偽造)

    這篇文章主要介紹了spring security中的csrf防御機制原理解析(跨域請求偽造),本文通過實例代碼詳解的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-12-12
  • java抓取鼠標(biāo)事件和鼠標(biāo)滾輪事件示例

    java抓取鼠標(biāo)事件和鼠標(biāo)滾輪事件示例

    這篇文章主要介紹了java抓取鼠標(biāo)事件和鼠標(biāo)滾輪事件示例,需要的朋友可以參考下
    2014-05-05
  • java中抽象類、抽象方法、接口與實現(xiàn)接口實例詳解

    java中抽象類、抽象方法、接口與實現(xiàn)接口實例詳解

    這篇文章主要給大家介紹了關(guān)于java中抽象類、抽象方法、接口與實現(xiàn)接口的相關(guān)資料,文中通過示例代碼將四者介紹的非常詳細,并且簡單介紹了抽象類和接口的區(qū)別,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • Spring Boot 中整合 MyBatis-Plus詳細步驟(最新推薦)

    Spring Boot 中整合 MyBatis-Plus詳細步驟(最新推薦)

    本文詳細介紹了如何在SpringBoot項目中整合MyBatis-Plus,包括整合步驟、基本CRUD操作、分頁查詢、批量操作、自定義SQL操作等,通過這些步驟,開發(fā)者可以快速實現(xiàn)數(shù)據(jù)庫操作,提高開發(fā)效率,感興趣的朋友一起看看吧
    2025-01-01
  • Java程序打印奧林匹克標(biāo)志方法詳解

    Java程序打印奧林匹克標(biāo)志方法詳解

    這篇文章主要介紹了Java程序打印奧林匹克標(biāo)志方法詳解,需要的朋友可以參考下。
    2017-09-09
  • SpringBoot項目使用jasypt加解密的方法

    SpringBoot項目使用jasypt加解密的方法

    jasypt是一個通用的加解密庫,我們可以使用它在配置文件中對數(shù)據(jù)庫密碼進行加密,以確保其安全性,接下來通過本文給大家介紹SpringBoot項目使用jasypt加解密的方法,感興趣的朋友一起看看吧
    2022-05-05

最新評論

沙雅县| 大邑县| 涡阳县| 阿鲁科尔沁旗| 专栏| 英山县| 乌拉特中旗| 泾阳县| 濮阳县| 鄂温| 焉耆| 琼海市| 镇赉县| 信丰县| 明溪县| 慈溪市| 哈巴河县| 龙海市| 民和| 葫芦岛市| 潞城市| 项城市| 泗阳县| 准格尔旗| 那曲县| 十堰市| 江西省| 肇源县| 桃园市| 射洪县| 澄迈县| 阜城县| 长垣县| 嘉祥县| 和林格尔县| 晴隆县| 宜阳县| 凤城市| 巴青县| 宁夏| 永平县|