久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    Spring Boot 優雅重啟知多少

     太極混元天尊 2018-05-22


    項目在重新發布的過程中,如果有的請求處理時間比較長,還沒執行完成,此時重啟的話就會導致請求中斷,影響業務功能,優雅重啟可以保證在停止的時候,不接收外部的新的請求,等待未完成的請求執行完成,這樣可以保證數據的完整性。

    Spring Boot 1.X

    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import org.apache.catalina.connector.Connector;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
    import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
    import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
    import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.event.ContextClosedEvent;
    /**
    * Spring Boot1.X Tomcat容器優雅停機
    * @author yinjihuan
    *
    */

    @Configuration
    public class ShutdownConfig {
       /**
        * 用于接受shutdown事件
        * @return
        */

       @Bean
       public GracefulShutdown gracefulShutdown() {
           return new GracefulShutdown();
       }
       /**
        * 用于注入 connector
        * @return
        */

       @Bean
       public EmbeddedServletContainerCustomizer tomcatCustomizer() {
           return new EmbeddedServletContainerCustomizer() {
               @Override
               public void customize(ConfigurableEmbeddedServletContainer container) {
                   if (container instanceof TomcatEmbeddedServletContainerFactory) {
                       ((TomcatEmbeddedServletContainerFactory) container).addConnectorCustomizers(gracefulShutdown());
                   }
               }
           };
       }
       private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListenerContextClosedEvent> {
           private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
           private volatile Connector connector;
           private final int waitTime = 120;
           @Override
           public void customize(Connector connector) {
               this.connector = connector;
           }
           @Override
           public void onApplicationEvent(ContextClosedEvent event) {
               this.connector.pause();
               Executor executor = this.connector.getProtocolHandler().getExecutor();
               if (executor instanceof ThreadPoolExecutor) {
                   try {
                       ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                       log.info('shutdown start');
                       threadPoolExecutor.shutdown();
                       log.info('shutdown end');
                       if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                           log.info('Tomcat 進程在' + waitTime + '秒內無法結束,嘗試強制結束');
                       }
                       log.info('shutdown success');
                   } catch (InterruptedException ex) {
                       Thread.currentThread().interrupt();
                   }
               }
           }
       }
    }

    Spring Boot 2.X

    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import org.apache.catalina.connector.Connector;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.event.ContextClosedEvent;
    /**
    * Spring Boot2.X Tomcat容器優雅停機
    * @author yinjihuan
    *
    */

    @Configuration
    public class ShutdownConfig {
       /**
        * 用于接受shutdown事件
        * @return
        */

       @Bean
       public GracefulShutdown gracefulShutdown() {
           return new GracefulShutdown();
       }
       @Bean
       public ServletWebServerFactory servletContainer() {
         TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
         tomcat.addConnectorCustomizers(gracefulShutdown());
         return tomcat;
       }
       private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListenerContextClosedEvent> {
           private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
           private volatile Connector connector;
           private final int waitTime = 120;
           @Override
           public void customize(Connector connector) {
               this.connector = connector;
           }
           @Override
           public void onApplicationEvent(ContextClosedEvent event) {
               this.connector.pause();
               Executor executor = this.connector.getProtocolHandler().getExecutor();
               if (executor instanceof ThreadPoolExecutor) {
                   try {
                       ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                       log.info('shutdown start');
                       threadPoolExecutor.shutdown();
                       log.info('shutdown end');
                       if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                           log.info('Tomcat 進程在' + waitTime + '秒內無法結束,嘗試強制結束');
                       }
                       log.info('shutdown success');
                   } catch (InterruptedException ex) {
                       Thread.currentThread().interrupt();
                   }
               }
           }
       }
    }

    重啟服務腳本:

    NG='zh_CN.UTF-8'
    pid=`ps ax | grep fangjia-youfang-web | grep java | head -1 | awk '{print $1}'`
    echo $pid
    #kill $pid
    curl -X POST http://127.0.0.1:8086/shutdown?token=認證信息
    while [[ $pid != '' ]]; do
       echo '服務停止中...'
       sleep 1
       pid=`ps ax | grep fangjia-youfang-web | grep java | head -1 | awk '{print $1}'`
    done
    echo '服務停止成功,開始重啟服務...'
    java -jar xxx.jar

    在重啟之前首先發送重啟命令到endpoint,或者用kill 進程ID的方式,千萬不要用kill -9。

    然后循環檢測進程是否存在,如果服務正常停止了,進程也就不存在了,如果進程還在,證明還有未處理完的請求,停止1秒,繼續檢測。

    關于重啟服務,建議用kill方式,這樣就不用依賴spring-boot-starter-actuator,如果用endpoint方式,則需要控制好權限,不然隨時都有可能被人重啟了,可以用security來控制權限,我這邊是自己用過濾器來控制的。

    如果用actuator方式重啟的話需要配置啟用重啟功能:
    1.x配置如下:

    endpoints.shutdown.enabled=true

    2.x配置就比較多了,默認只暴露了幾個常用的,而且訪問地址也有變化,比如health, 以前是直接訪問/health,現在需要 /actuator/health才能訪問。我們可以通過配置來兼容之前的訪問地址。

    shutdown默認是不暴露的,可以通過配置暴露并開始,配置如下:

    #訪問路徑,配置后就和1.x版本路徑一樣
    management.endpoints.web.base-path=/
    # 暴露所有,也可以暴露單個或多個
    management.endpoints.web.exposure.include=*
    # 開啟shutdown
    management.endpoint.shutdown.enabled=true

    文檔請參考:https://docs./spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/#production-ready

    如何測試

    測試的話我們可以寫一個簡單的接口,在接口中等待,然后執行腳本停止項目,如果正常的話會輸出服務停止中,等到你的接口執行完成,進程才會消失掉,但是如果超過了你配置的等待時間就會強行退出。

    @GetMapping('/hello')
    public String hello() {
       System.out.println('req.........');
       try {
           Thread.sleep(1000 * 60 * 3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return 'hello';
    }

    需要注意的問題

    如果你的項目中有用到其他的線程池,比如Spring的ThreadPoolTaskExecutor,不熟悉的同學可以參考我的這篇文章《Spring Boot Async異步執行》

    在發送停止命令后如果ThreadPoolTaskExecutor有線程還沒處理完的話,這個時候進程是不會自動關閉的。這個時候我們需要對線程池進行關閉處理,增加代碼如下:

    AsyncTaskExecutePool asyncTaskExecutePool = event.getApplicationContext().getBean(AsyncTaskExecutePool.class);
    Executor executors = asyncTaskExecutePool.getAsyncExecutor();
    try {
         if (executors instanceof ThreadPoolTaskExecutor) {
              ThreadPoolTaskExecutor threadPoolExecutor = (ThreadPoolTaskExecutor) executors;
              log.info('Async shutdown start');
              threadPoolExecutor.setWaitForTasksToCompleteOnShutdown(true);
              threadPoolExecutor.setAwaitTerminationSeconds(waitTime);
              threadPoolExecutor.shutdown();
         }
    } catch (Exception ex) {
        Thread.currentThread().interrupt();
    }

    ThreadPoolTaskExecutor只有shutdown方法,沒有awaitTermination方法,通過查看源碼,在shutdown之前設置setWaitForTasksToCompleteOnShutdown和setAwaitTerminationSeconds同樣能實現awaitTermination。

    源碼如下:

    public void shutdown() {
           if (logger.isInfoEnabled()) {
               logger.info('Shutting down ExecutorService' + (this.beanName != null ? ' '' + this.beanName + ''' : ''));
           }
           if (this.executor != null) {
               if (this.waitForTasksToCompleteOnShutdown) {
                   this.executor.shutdown();
               }
               else {
                   for (Runnable remainingTask : this.executor.shutdownNow()) {
                       cancelRemainingTask(remainingTask);
                   }
               }
               awaitTerminationIfNecessary(this.executor);
           }
       }

    當waitForTasksToCompleteOnShutdown為true的時候就直接調用executor.shutdown();,最后執行awaitTerminationIfNecessary方法。

    private void awaitTerminationIfNecessary(ExecutorService executor) {
           if (this.awaitTerminationSeconds > 0) {
               try {
                   if (!executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) {
                       if (logger.isWarnEnabled()) {
                           logger.warn('Timed out while waiting for executor' +
                                   (this.beanName != null ? ' '' + this.beanName + ''' : '') + ' to terminate');
                       }
                   }
               }
               catch (InterruptedException ex) {
                   if (logger.isWarnEnabled()) {
                       logger.warn('Interrupted while waiting for executor' +
                               (this.beanName != null ? ' '' + this.beanName + ''' : '') + ' to terminate');
                   }
                   Thread.currentThread().interrupt();
               }
           }
       }

    awaitTerminationIfNecessary中會判斷屬性awaitTerminationSeconds 如果與值的話就執行關閉等待檢測邏輯,跟我們處理tomcat關閉的代碼是一樣的。

    發現這樣做之后好像沒什么效果,于是我換了一種寫法,直接通過獲取ThreadPoolTaskExecutor中的ThreadPoolExecutor來執行關閉邏輯:

    AsyncTaskExecutePool asyncTaskExecutePool = event.getApplicationContext().getBean(AsyncTaskExecutePool.class);
    Executor executors = asyncTaskExecutePool.getAsyncExecutor();
    try {
         if (executors instanceof ThreadPoolTaskExecutor) {
               ThreadPoolTaskExecutor threadPoolExecutor = (ThreadPoolTaskExecutor) executors;
               log.info('Async shutdown start');
               threadPoolExecutor.getThreadPoolExecutor().shutdown();
               log.info('Async shutdown end'+threadPoolExecutor.getThreadPoolExecutor().isTerminated());
               if (!threadPoolExecutor.getThreadPoolExecutor().awaitTermination(waitTime, TimeUnit.SECONDS)) {
                   log.info('Tomcat 進程在' + waitTime + '秒內無法結束,嘗試強制結束');
               }
               log.info('Async shutdown success');
          }
    } catch (Exception ex) {
         Thread.currentThread().interrupt(); 

      本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 18禁无遮挡啪啪无码网站破解版| 日日躁夜夜躁狠狠躁超碰97 | 久久综合九色综合97婷婷| 久9视频这里只有精品| 国精产品一区二区三区有限公司| 日韩中文字幕有码av| 色综合 图片区 小说区| 亚洲AV无码之国产精品网址 | 丰满爆乳在线播放| 精品久久久久中文字幕日本| 午夜免费无码福利视频麻豆| 久久99精品国产99久久6尤物 | 国产成人久久综合一区| 久久精品人妻中文系列葵司| 一区二区国产高清视频在线| 日本欧美一区二区三区在线播放 | 日日躁狠狠躁狠狠爱| 精品无码国产自产在线观看水浒传| 国产福利在线观看免费第一福利| 88国产精品欧美一区二区三区| 日夜啪啪一区二区三区| 影音先锋人妻啪啪AV资源网站| 无码日韩做暖暖大全免费不卡| 强奷乱码中文字幕熟女导航| 在线观看成人年视频免费 | 国产精品美女乱子伦高潮| 成 人影片免费观看| 久久精品免视看国产成人| 亚洲乱亚洲乱少妇无码| 亚洲理论在线A中文字幕| 国产精品国产三级国产AV中文| 国产做无码视频在线观看| 一区二区三区精品不卡| 99精品电影一区二区免费看| 国产高清色高清在线观看 | 国产微拍精品一区二区| 亚洲男女羞羞无遮挡久久丫| 国产仑乱无码内谢| 88国产精品视频一区二区三区| 国产娱乐凹凸视觉盛宴在线视频| 亚洲欧洲日韩国内精品|