十一、 Hystrix的高階知識

11.1 request collapser請求合併技術

第六章(Request Cache請求緩存)優化過一個批量查詢的接口了,request cache來做優化,相同的請求參數就可以直接取用緩存了的記錄。但仍需要一次業務操作接口中需要發送多次網絡請求,調用多次接口,才能拿到結果。可以使用HystrixCollapser將多個HystrixCommand合併到一起,多個command放在一個command裏面去執行,發送一次網絡請求,就拉取到多條數據。用請求合併技術,將多個請求合併起來,可以減少高併發訪問下需要使用的線程數量以及網絡連接數量,這都是hystrix自動進行的,其實對於高併發的訪問來說,是可以提升性能。

 

 

 

 

  • 請求合併有很多種級別:

(1)global context,tomcat所有調用線程,對一個依賴服務的任何一個command調用都可以被合併在一起,hystrix就傳遞一個HystrixRequestContext。

(2)user request context,tomcat內某一個調用線程,將某一個tomcat線程對某個依賴服務的多個command調用合併在一起

(3)object modeling,基於對象的請求合併,如果有幾百個對象,遍歷後依次調用每個對象的某個方法,可能導致發起幾百次網絡請求,基於hystrix可以自動將對多個對象模型的調用合併到一起。

 

  • 請求合併的優缺點(不針對那種訪問延時特別低的請求的):

缺點:使用請求合併技術的開銷就是導致延遲大幅度增加,因爲需要一定的時間將多個請求合併起來。

優點:可以大幅度削減你的線程池的資源耗費,線程池。減少你對後端服務訪問時的網絡資源的開銷。

 

  • 請求合併模式及參數選擇

將多個command請求合併到一個command中執行

請求合併時,可以設置一個batch size,以及elapsed time(控制什麼時候觸發合併後的command執行)

有兩種合併模式,一種是request scope,另一種是global scope,默認是rquest scope,在collapser構造的時候指定scope模式。

request scope的batch收集是建立在一個request context內的,而global scope的batch收集是橫跨多個request context的,所以對於global context來說,必須確保能在一個command內處理多個requeset context的請求

在netflix,是隻用request scope請求合併的,因爲默認是用唯一一個request context包含所有的command,所以要做合併,肯定就是request scope。請求合併技術,對於那種訪問同一個資源的command,但是參數不同,是很有效的。

 

public static void main(String[] args) {

        String associatedIds = "1,2,3,3,4,4,5";

        String url ="http://localhost:……?associatedIds=";

        List<Future<String>> futures = new ArrayList<Future<String>>();

        for(String associatedId : associatedIds.split(",")) {

            JoinExamWholeAnalysisCollapser getUserInfosCollapser =

                    new JoinExamWholeAnalysisCollapser(associatedId,url);

            futures.add(getUserInfosCollapser.queue());

        }

        try {

            for(Future<String> future : futures) {

                System.out.println("CacheController的結果:" + future.get()); 

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

 

 

 

一個批量的查詢ids過來以後,我們還是多個command的方式去執行,request collapser+request cache,相同的查詢id還是就查詢一次,不同的商品合併到一起通過一個網絡請求得到結果。

private final class BatchCommand extends HystrixCommand<List<String>>{

        public final Collection<CollapsedRequest<String,String>> requests;

        private String url;

       

        public BatchCommand(Collection<CollapsedRequest<String,String>> requests, String url) {

            super(Setter

                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("AssociatedExamAllCourses"))

                    .andCommandKey(HystrixCommandKey.Factory.asKey("JoinExamWholeAnalysisesCollapserBatchCommand"))

                    );

            this.requests = requests;

            this.url = url;

        }

 

        @Override

        protected List<String> run() throws Exception {

            StringBuilder paramsBuilder = new StringBuilder("");

            for(CollapsedRequest<String, String> request : requests) {

                paramsBuilder.append(request.getArgument()).append(",");

            }

            String params = paramsBuilder.toString();

            params = params.substring(0, params.length() - 1);

            // 在這裏,我們可以做到什麼呢,將多個id合併在一個batch內,直接發送一次網絡請求,獲取到所有的結果

            String batchurl = url+"?associatedIds=" + params;

            String response = HttpClientUtils.simpleGetInvoke(batchurl, null);

            List<String> results = JSONArray.parseArray(response, String.class);

            for(String result : results) {

                System.out.println("BatchCommand內部,associatedId=" + result);

            }

            return results;

        }

    }

 

HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType>

public class JoinExamWholeAnalysisCollapser extends HystrixCollapser <List<String>, String, String>{

    private String associatedId;

    private String url;

    public JoinExamWholeAnalysisCollapser(String associatedId,String url) {

        super(Setter

                .withCollapserKey(HystrixCollapserKey.Factory.asKey("JoinExamWholeAnalysisCollapser"))

                .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()

            //控制一個Batch中最多允許多少個request被合併,然後纔會觸發一個batch的執行

            //默認值是無限大,就是不依靠這個數量來觸發執行,而是依靠時間

                           .withMaxRequestsInBatch(100)

             //控制一個batch創建之後,多長時間以後就自動觸發batch的執行,默認是10毫秒

                           .withTimerDelayInMilliseconds(20)

                )

              );

        this.associatedId = associatedId;

        this.url = url;

    }

    @Override

    public String getRequestArgument() {

        return associatedId;

    }

    @Override

    protected HystrixCommand<List<String>> createCommand(Collection<com.netflix.hystrix.HystrixCollapser.CollapsedRequest<String, String>> requests) {

        StringBuilder paramsBuilder = new StringBuilder("");

        for(CollapsedRequest<String, String> request : requests) {

            paramsBuilder.append(request.getArgument()).append(",");

        }

        String params = paramsBuilder.toString();

        params = params.substring(0, params.length() - 1);

        System.out.println("createCommand方法執行,params=" + params);

        return new BatchCommand(requests,url);

    }

    @Override

    protected void mapResponseToRequests(List<String> batchResponse, Collection<com.netflix.hystrix.HystrixCollapser.CollapsedRequest<String, String>> requests) {

        int count = 0;

        for(CollapsedRequest<String, String> request : requests) {

            request.setResponse(batchResponse.get(count++)); 

        }

    }

    /**

     * HystrixCommandHystrixObservableCommand都可以指定一個緩存key

     * 然後hystrix會自動進行緩存,接着在同一個request context內,再次訪問的時候,就會直接取用緩存用請求緩存,

     * 可以避免重複執行網絡請求

     */

    @Override

    protected String getCacheKey() {

        return "joint_examWholeAnalysis_" + associatedId;

    }

  }

 

timeout問題解釋:開發機上,特別慢,第一次請求的時候,幾百毫秒,默認的timeout時長比較短,第二次的時候,訪問的速度會快很多,就不會超時了

反應在系統上,第一次啓動的時候,會有個別的超時,但是後面就好了,手動將timeout時長設置的大一些。

 

(1)maxRequestsInBatch

控制一個Batch中最多允許多少個request被合併,然後纔會觸發一個batch的執行

默認值是無限大,就是不依靠這個數量來觸發執行,而是依靠時間

HystrixCollapserProperties.Setter()

   .withMaxRequestsInBatch(int value)

 

(2)timerDelayInMilliseconds

控制一個batch創建之後,多長時間以後就自動觸發batch的執行,默認是10毫秒

HystrixCollapserProperties.Setter()

   .withTimerDelayInMilliseconds(int value)

super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("GetUserInfosCollapser"))

                                     .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()

                                                           .withMaxRequestsInBatch(100)

                                                           .withTimerDelayInMilliseconds(20)));

public JoinExamWholeAnalysisCollapser(String associatedId,String url) {

        super(Setter

                .withCollapserKey(HystrixCollapserKey.Factory.asKey("JoinExamWholeAnalysisCollapser"))

                .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()

                //控制一個Batch中最多允許多少個request被合併,然後纔會觸發一個batch的執行

                //默認值是無限大,就是不依靠這個數量來觸發執行,而是依靠時間

                           .withMaxRequestsInBatch(100)

                //控制一個batch創建之後,多長時間以後就自動觸發batch的執行,默認是10毫秒

                           .withTimerDelayInMilliseconds(20)

                )

              );

        this.associatedId = associatedId;

        this.url = url;

    }

 

11.2 fail-fast和fail-slient高階容錯模式

fail-fast:就是不給fallback降級邏輯,HystrixCommand.run(),直接報錯,直接會把這個報錯拋出來。

 

public class FailureModeCommand extends HystrixCommand<Boolean> {

         private boolean failure;

         public FailureModeCommand(boolean failure) {

                   super(HystrixCommandGroupKey.Factory.asKey("FailureModeGroup"));

                   this.failure = failure;

         }

         @Override

         protected Boolean run() throws Exception {

                   if(failure) {

                            throw new Exception();

                   }

                   return true;

         }

}

 

public class FailureModeCommandTest {

        

         public static void main(String[] args) {

                   try {

                            FailureModeCommand failureModeCommand = new FailureModeCommand(true);

                            failureModeCommand.execute();

                   } catch (Exception e) {

                            e.printStackTrace();

                   }

         }

        

}

 

fail-silent:給一個fallback降級邏輯,如果HystrixCommand.run()報錯了,會走fallback降級,直接返回一個空值;

HystrixCommand,就給一個null;

HystrixObservableCommand,Observable.empty()。

public class FailureModeCommand extends HystrixCommand<Boolean> {

         private boolean failure;

         public FailureModeCommand(boolean failure) {

                  super(HystrixCommandGroupKey.Factory.asKey("FailureModeGroup"));

                   this.failure = failure;

         }

         @Override

         protected Boolean run() throws Exception {

                   if(failure) {

                            throw new Exception();

                   }

                   return true;

         }

 

@Override

         protected Boolean getFallback() {

                   return false;

         }

}

 

public class FailureModeCommandTest {

        

         public static void main(String[] args) {

                   try {

                            FailureModeCommand failureModeCommand = new FailureModeCommand(true);

                            System.out.println(failureModeCommand.execute());

                  } catch (Exception e) {

                            e.printStackTrace();

                   }

         }

        

}

 

11.3 stubbed fallback高階降級模式

stubbed fallback,殘缺的降級。用請求中的部分數據拼裝成結果,然後再填充一些默認值,返回。比如說你發起了一個請求,然後請求中參數可能本身就附帶了一些信息,如果主請求失敗了,走到降級邏輯。在降級邏輯裏面,可以將這個請求中的數據,以及部分本地緩存有的數據拼裝在一起,再給數據填充一些簡單的默認值,然後儘可能將自己有的數據返回到請求方。

         @Override

         protected UserInfo getFallback() {

                   UserInfo UserInfo = new UserInfo();

                   // 從請求參數中獲取到的唯一條數據

                   UserInfo.setId(userId);

                   // 從本地緩存Ehcache中獲取一些數據

                   UserInfo.setName(UserCache.getName(UserInfo.getNameById(userId))); 

                   UserInfo.setCityId(UserCache.getCityByUserId(userId));

                   // 手動填充一些默認的數據

                   UserInfo.setName("默認商品"); 

                   UserInfo.setPicture ("default.jpg");   

                   return UserInfo;

         }

 

<!-- 緩存配置 -->

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

       <property name="configLocation" value="classpath: cache/ehcache-local.xml" />

    </bean>

 

<!—用戶緩存配置. -->

<userCache maxEntriesLocalHeap="100" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"

overflowToDisk="true" maxEntriesLocalDisk="100000" />

 

<diskStore>   : 當內存緩存中對象數量超過maxElementsInMemory,將緩存對象寫到磁盤緩存中(需對象實現序列化接口)  
<diskStore path="">     : 用來配置磁盤緩存使用的物理路徑,Ehcache磁盤緩存使用的文件後綴名是*.data*.index  
name : "緩存名稱,cache的唯一標識(ehcache會把這個cache放到HashMap)  
maxElementsInMemory  : 緩存最大個數。
eternal="false"   : 對象是否永久有效,一但設置了,timeout將不起作用。 (必須設置)
maxEntriesLocalHeap="1000"  : 堆內存中最大緩存對象數,0沒有限制(必須設置)
maxEntriesLocalDisk= "1000"   : 硬盤最大緩存個數。
overflowToDisk="false"   : 當緩存達到maxElementsInMemory值是,是否允許溢出到磁盤(必須設置)(內存不足時,是否啓用磁盤緩存。)
diskSpoolBufferSizeMB  : 這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩衝區。
diskPersistent="false"  : 磁盤緩存在JVM重新啓動時是否保持(默認爲false)
timeToIdleSeconds="0" : 導致元素過期的訪問間隔(秒爲單位),即當緩存閒置n秒後銷燬。 eternalfalse時,這個屬性纔有效,0表示可以永遠空閒,默認爲0
timeToLiveSeconds="600" : 元素在緩存裏存在的時間(秒爲單位),即當緩存存活n秒後銷燬. 0 表示永遠存在不過期
memoryStoreEvictionPolicy="LFU" : 當達到maxElementsInMemory,如何強制進行驅逐默認使用"最近使用(LRU)"策略,其它還有先入先出FIFO,最少使用LFU,較少使用LRU
diskExpiryThreadIntervalSeconds :磁盤失效線程運行時間間隔,默認是120秒。
clearOnFlush   : 內存數量最大時是否清除。

 

 

public class CacheUtils { 

     private static CacheManager cacheManager = ((CacheManager)SpringContextHolder.getBean("cacheManager"));

 

private static Cache getCache(String cacheName){

       Cache cache = cacheManager.getCache(cacheName);

       if (cache == null){

           cacheManager.addCache(cacheName);

           cache = cacheManager.getCache(cacheName);

           cache.getCacheConfiguration().setEternal(true);

       }

       return cache;

  }

}

 

11.4 雙層嵌套command實現的發送網絡請求的降級模式

多級降級:command嵌套command,先降一級,嘗試用一個備用方案去執行,如果備用方案失敗了,再用最後下一個備用方案去執行。

 

常見的多級降級的做法,有一個操作,要訪問MySQL數據庫

mysql數據庫訪問報錯,降級,去redis中獲取數據

如果說redis又掛了,然後就去從本地ehcache緩存中獲取數據

 

多級降級的策略:command,fallback,又套了一個command,第二個command其實是第一級降級策略。

public class GetUserInfoInfoCommand extends HystrixCommand<UserInfoInfo> {

 

    public static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetUserInfoInfoCommand");

    private Long userInfoId;

    public GetUserInfoInfoCommand(Long userInfoId) {

    super(HystrixCommandGroupKey.Factory.asKey("UserInfoInfoService"));

        this.userInfoId = userInfoId;

    }

 

    @Override

    protected UserInfoInfo run() throws Exception {

        String url = "http://127.0.0.1:8082/getUserInfoInfo?userInfoId=" + userInfoId;

        String response = HttpClientUtils.sendGetRequest(url);

        return JSONObject.parseObject(response, UserInfoInfo.class);

    }

 

    @Override

    protected UserInfoInfo getFallback() {

        return new FirstLevelFallbackCommand(userInfoId).execute();

    }

    private static class FirstLevelFallbackCommand extends HystrixCommand<UserInfoInfo> {

 

        private Long userInfoId;

        public FirstLevelFallbackCommand(Long userInfoId) {

// 第一級的降級策略,因爲這個command是運行在fallback中的

// 所以至關重要的一點是,在做多級降級的時候,要將降級command的線程池單獨做一個出來

// 如果主流程的command都失敗了,可能線程池都已經被佔滿了

// 降級command必須用自己的獨立的線程池

            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserInfoInfoService")).andCommandKey(HystrixCommandKey.Factory.asKey("FirstLevelFallbackCommand")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("FirstLevelFallbackPool")));

            this.userInfoId = userInfoId;

        }

        @Override

        protected UserInfoInfo run() throws Exception {

            // 這裏,因爲是第一級降級的策略,所以說呢,其實是要從備用機房的機器去調用接口

            // 但是,我們這裏沒有所謂的備用機房,所以說還是調用同一個服務來模擬

            String url = "http://127.0.0.1:8082/getUserInfoInfo?userInfoId=" + userInfoId;

            String response = HttpClientUtils.sendGetRequest(url);

            return JSONObject.parseObject(response, UserInfoInfo.class);

        }

        @Override

        protected UserInfoInfo getFallback() {

            UserInfo UserInfo = new UserInfo();

            // 從請求參數中獲取到的唯一條數據

            UserInfo.setId(userId);

            // 從本地緩存Ehcache中獲取一些數據

            UserInfo.setName(UserCache.getName(UserInfo.getNameById(userId)));

            UserInfo.setCityId(UserCache.getCityByUserId(userId));

            // 手動填充一些默認的數據

            UserInfo.setName("默認商品");

            UserInfo.setPicture("default.jpg");

            return UserInfo;

        }

    }

}

 

11.5 基於facade command的服務接口的手動降級機制

在一個command它的主流程中,根據一個標識位,判斷要執行哪個業務流程。如果現在知道有問題了,希望能夠手動降級的話,動態給服務發送個請求在請求中修改標識位,自動就讓command以後都直接過來執行備用command。

 

一般會有3個command,套在最外面的command,是用semaphore信號量做限流和資源隔離的,因爲這個command不用去care timeout的問題,嵌套調用的command會自己去管理timeout超時的。

 

降級標誌位:

public class IsDegrade {

    private static boolean degrade = false;

    public static boolean isDegrade() {

       return degrade;

    }

    public static void setDegrade(boolean degrade) {

       IsDegrade.degrade = degrade;

    }

}

 

套在最外面的command,是用semaphore信號量做限流和資源隔離的,不用去care timeout的問題。

public class GetUserInfoInfoFacadeCommand extends HystrixCommand<UserInfoInfo> {

    private Long userInfoId;

    public GetUserInfoInfoFacadeCommand(Long userInfoId) {

    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserInfoInfoService"))

    .andCommandKey(HystrixCommandKey.Factory.asKey("GetUserInfoInfoFacadeCommand"))

                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()

                .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

                     .withExecutionIsolationSemaphoreMaxConcurrentRequests(15)));

        this.userInfoId = userInfoId;

    }

    @Override

    protected UserInfoInfo run() throws Exception {

        if(!IsDegrade.isDegrade()) {

            return new GetUserInfoInfoCommand(userInfoId).execute();

        } else {

            return new GetUserInfoInfoFromMySQLCommand(userInfoId).execute();

        }

    }

   

    @Override

    protected UserInfoInfo getFallback() {

        return new UserInfoInfo();

    }

}

 

11.6 生產環境中的線程池大小以及timeout配置優化經驗

(1)一開始先不要設置timeout超時時長,默認就是1000ms,也就是1s

(2)一開始也不要設置線程池大小,默認就是10

(3)直接部署hystrix到生產環境,如果運行的很良好,那麼就讓它這樣運行好了

(4)讓hystrix應用,24小時運行在生產環境中

(5)依賴標準的監控和報警機制來捕獲到系統的異常運行情況

(6)在24小時之後,看一下調用延遲的佔比,以及流量,來計算出讓短路器生效的最小的配置數字

(7)直接對hystrix配置進行熱修改,然後繼續在hystrix dashboard上監控

(8)看看修改配置後的系統表現有沒有改善

 

下面是根據系統表現優化和調整線程池大小,隊列大小,信號量容量,以及timeout超時時間的經驗

 

假設對一個依賴服務的高峯調用QPS是每秒30次

一開始如果默認的線程池大小是10

我們想的是,理想情況下,每秒的高峯訪問次數 * 99%的訪問延時 + buffer = 30 * 0.2 + 4 = 10線程,10個線程每秒處理30次訪問應該足夠了,每個線程處理3次訪問

此時,我們合理的timeout設置應該爲300ms,也就是99.5%的訪問延時,計算方法是,因爲判斷每次訪問延時最多在250ms(TP99如果是200ms的話),再加一次重試時間50ms,就是300ms,感覺也應該足夠了

 

因爲如果timeout設置的太多了,比如400ms,比如如果實際上,在高峯期,還有網絡情況較差的時候,可能每次調用要耗費350ms,也就是達到了最長的訪問時長

那麼每個線程處理2個請求,就會執行700ms,然後處理第三個請求的時候,就超過1秒鐘了,此時會導致線程池全部被佔滿,都在處理請求

 

這個時候下一秒的30個請求再進來了,那麼就會導致線程池已滿,拒絕請求的情況,就會調用fallback降級機制。因此對於短路器來說,timeout超時一般應該設置成TP99.5,比如設置成300ms,那麼可以確保說,10個線程,每個線程處理3個訪問,每個訪問最多就允許執行300ms,過時就timeout了,這樣才能保證說每個線程都在1s內執行完,纔不會導致線程池被佔滿,然後後續的請求過來大量的reject

對於線程池大小來說,一般應該控制在10個左右,20個以內,最少5個,不要太多,也不要太少

 

大家可能會想,每秒的高峯訪問次數是30次,如果是300次,甚至是3000次,30000次呢???

30000 * 0.2 = 6000 + buffer = 6100,一個服務器內一個線程池給6000個線程把

如果你一個依賴服務佔據的線程數量太多的話,會導致其他的依賴服務對應的線程池裏沒有資源可以用了

6000 / 20 = 300臺虛擬機也是ok的

虛擬機,4個cpu core,4G內存,虛擬機,300臺

物理機,十幾個cpu core,幾十個G的內存,5~8個虛擬機,300個虛擬機 = 50臺物理機

11.7 線程池的自動化動態擴容與縮容技術

  • 彈性的線程資源調度的模式

可能會出現一種情況,比如說我們的某個依賴,在高峯期,需要耗費100個線程,但是在那個時間段,剛好其他的依賴的線程池其實就維持一兩個就可以了。但是,如果我們都是設置死的,每個服務就給10個線程,那就很坑,可能就導致有的服務在高峯期需要更多的資源,但是沒資源了,導致很多的reject。但是其他的服務,每秒鐘就易一兩個請求,結果也佔用了10個線程,佔着茅坑不拉屎

剛開始的時候,每個依賴服務都是給1個線程,3個線程,但是我們允許說,如果你的某個線程池突然需要大量的線程,最多可以到100個線程。如果你使用了100個線程,高峯期過去了,自動將空閒的線程給釋放掉。

 

(1)coreSize

設置線程池的大小,默認是10

HystrixThreadPoolProperties.Setter() .withCoreSize(int value)

HystrixThreadPoolProperties.Setter().withCoreSize(10)

//CoreSize:設置線程池的大小,默認是10,如果你不設置另外兩個queue相關的參數,等待隊列是關閉

 

(2)maximumSize

設置線程池的最大大小,只有在設置allowMaximumSizeToDivergeFromCoreSize的時候才能生效,默認是10。

HystrixThreadPoolProperties.Setter()

   .withMaximumSize(int value)

//設置線程池的最大大小,只有在設置allowMaximumSizeToDivergeFromCoreSize的時候才能生效

.withMaximumSize(15)

 

(3)allowMaximumSizeToDivergeFromCoreSize

允許線程池大小自動動態調整,設置爲true之後,maxSize就生效了,此時如果一開始是coreSize個線程,隨着併發量上來,那麼就會自動獲取新的線程,但是如果線程在keepAliveTimeMinutes內空閒,就會被自動釋放掉

默認是fales

 

HystrixThreadPoolProperties.Setter().withAllowMaximumSizeToDivergeFromCoreSize(boolean value)

//允許線程池大小自動動態調整(默認爲false),設置爲true之後,maxSize就生效了,此時如果一開始是coreSize個線程,隨着併發量上來,那麼就會自動獲取新的線程,

但是如果線程在keepAliveTimeMinutes內空閒,就會被自動釋放掉

.withAllowMaximumSizeToDivergeFromCoreSize(true)

 

(4)keepAliveTimeMinutes

設置保持存活的時間,單位是分鐘,默認是1

如果設置allowMaximumSizeToDivergeFromCoreSize爲true,那麼coreSize就不等於maxSize,此時線程池大小是可以動態調整的,可以獲取新的線程,也可以釋放一些線程

如果coreSize < maxSize,那麼這個參數就設置了一個線程多長時間空閒之後,就會被釋放掉

//如果coreSize < maxSize,那麼這個參數就設置了一個線程多長時間空閒之後,就會被釋放掉

.withKeepAliveTimeMinutes(1)

 

生產環境中,這塊怎麼玩兒的?

也是根據你的服務的實際的運行的情況切看的,比如說你發現某個服務,平時3個併發QPS就夠了,高峯期可能要到30個,那麼你就可以給設置彈性的資源調度。

注:因爲有可能一個服務會有多個線程池,你要計算好,每個線程池的最大的大小加起來不能過大,30個依賴,30個線程池,每個線程池最大給到30,900個線程,很坑的

 

還有一種模式,就是說讓多個依賴服務共享一個線程池,我們不推薦,多個依賴服務就做不到資源隔離,互相之間會影響的

11.8 Hystrix的metric高階配置

1、爲什麼需要監控與報警?

 

HystrixCommand執行的時候,會生成一些執行耗時等方面的統計信息。這些信息對於系統的運維來說,是很有幫助的,因爲我們通過這些統計信息可以看到整個系統是怎麼運行的。hystrix對每個command key都會提供一份metric,而且是秒級統計粒度的。

 

這些統計信息,無論是單獨看,還是聚合起來看,都是很有用的。如果將一個請求中的多個command的統計信息拿出來單獨查看,包括耗時的統計,對debug系統是很有幫助的。聚合起來的metric對於系統層面的行爲來說,是很有幫助的,很適合做報警或者報表。hystrix dashboard就很適合。

 

2、hystrix的事件類型

 

對於hystrix command來說,只會返回一個值,execute只有一個event type,fallback也只有一個event type,那麼返回一個SUCCESS就代表着命令執行的結束

對於hystrix observable command來說,多個值可能被返回,所以emit event代表一個value被返回,success代表成功,failure代表異常

 

(1)execute event type

 

EMIT                                              observable command返回一個value

SUCCESS                                      完成執行,並且沒有報錯

FAILURE                                        執行時拋出了一個異常,會觸發fallback

TIMEOUT                                               開始執行了,但是在指定時間內沒有完成執行,會觸發fallback

BAD_REQUEST                                     執行的時候拋出了一個HystrixBadRequestException

SHORT_CIRCUITED                   短路器打開了,觸發fallback

THREAD_POOL_REJECTED      線程成的容量滿了,被reject,觸發fallback

SEMAPHORE_REJECTED          信號量的容量滿了,被reject,觸發fallback

 

(2)fallback event type

 

FALLBACK_EMIT                         observable command,fallback value被返回了

FALLBACK_SUCCESS                  fallback邏輯執行沒有報錯

FALLBACK_FAILURE          fallback邏輯拋出了異常,會報錯

FALLBACK_REJECTION              fallback的信號量容量滿了,fallback不執行,報錯

FALLBACK_MISSING                  fallback沒有實現,會報錯

 

(3)其他的event type

 

EXCEPTION_THROWN              command生命自週期是否拋出了異常

RESPONSE_FROM_CACHE       command是否在cache中查找到了結果

COLLAPSED                                  command是否是一個合併batch中的一個

 

(4)thread pool event type

 

EXECUTED                                    線程池有空間,允許command去執行了

REJECTED                                   線程池沒有空間,不允許command執行,reject掉了

 

(5)collapser event type

 

BATCH_EXECUTED                    collapser合併了一個batch,並且執行了其中的command

ADDED_TO_BATCH                            command加入了一個collapser batch

RESPONSE_FROM_CACHE                沒有加入batch,而是直接取了request cache中的數據

 

3、metric storage

 

metric被生成之後,就會按照一段時間來存儲,存儲了一段時間的數據纔會推送到其他系統中,比如hystrix dashboard

 

另外一種方式,就是每次生成metric就實時推送metric流到其他地方,但是這樣的話,會給系統帶來很大的壓力(不建議採用)

 

hystrix的方式是將metric寫入一個內存中的數據結構中,在一段時間之後就可以查詢到

 

hystrix 1.5x之後,採取的是爲每個command key都生成一個start event和completion event流,而且可以訂閱這個流。每個thread pool key也是一樣的,包括每個collapser key也是一樣的。

 

每個command的event是發送給一個線程安全的RxJava中的rx.Subject,因爲是線程安全的,所以不需要進行線程同步

 

因此每個command級別的,threadpool級別的,每個collapser級別的,event都會發送到對應的RxJava的rx.Subject對象中。這些rx.Subject對象接着就會被暴露出Observable接口,可以被訂閱。

 

4、metric統計相關的配置

 

(1)metrics.rollingStats.timeInMilliseconds

 

設置統計的rolling window,單位是毫秒,hystrix只會維持這段時間內的metric供短路器統計使用

 

這個屬性是不允許熱修改的,默認值是10000,就是10秒鐘

 

HystrixCommandProperties.Setter()

   .withMetricsRollingStatisticalWindowInMilliseconds(int value)

 

(2)metrics.rollingStats.numBuckets

 

該屬性設置每個滑動窗口被拆分成多少個bucket,而且滑動窗口對這個參數必須可以整除,同樣不允許熱修改

 

默認值是10,也就是說,每秒鐘是一個bucket

 

隨着時間的滾動,比如又過了一秒鐘,那麼最久的一秒鐘的bucket就會被丟棄,然後新的一秒的bucket會被創建

 

HystrixCommandProperties.Setter()

   .withMetricsRollingStatisticalWindowBuckets(int value)

 

(3)metrics.rollingPercentile.enabled

 

控制是否追蹤請求耗時,以及通過百分比方式來統計,默認是true

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileEnabled(boolean value)

 

(4)metrics.rollingPercentile.timeInMilliseconds

 

設置rolling window被持久化保存的時間,這樣才能計算一些請求耗時的百分比,默認是60000,60s,不允許熱修改

 

相當於是一個大的rolling window,專門用於計算請求執行耗時的百分比

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileWindowInMilliseconds(int value)

 

(5)metrics.rollingPercentile.numBuckets

 

設置rolling percentile window被拆分成的bucket數量,上面那個參數除以這個參數必須能夠整除,不允許熱修改

 

默認值是6,也就是每10s被拆分成一個bucket

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileWindowBuckets(int value)

 

(6)metrics.rollingPercentile.bucketSize

 

設置每個bucket的請求執行次數被保存的最大數量,如果再一個bucket內,執行次數超過了這個值,那麼就會重新覆蓋從bucket的開始再寫

 

舉例來說,如果bucket size設置爲100,而且每個bucket代表一個10秒鐘的窗口,但是在這個bucket內發生了500次請求執行,那麼這個bucket內僅僅會保留100次執行

 

如果調大這個參數,就會提升需要耗費的內存,來存儲相關的統計值,不允許熱修改

 

默認值是100

 

HystrixCommandProperties.Setter()

   .withMetricsRollingPercentileBucketSize(int value)

 

(7)metrics.healthSnapshot.intervalInMilliseconds

 

控制成功和失敗的百分比計算,與影響短路器之間的等待時間,默認值是500毫秒

 

HystrixCommandProperties.Setter()

   .withMetricsHealthSnapshotIntervalInMilliseconds(int value)

11.9 基於hystrix dashboard的可視化分佈式系統監控

1、安裝metrics stream POM

<dependency>

    <groupId>com.netflix.hystrix</groupId>

    <artifactId>hystrix-metrics-event-stream</artifactId>

    <version>1.4.10</version>

</dependency>

 

2、在系統入口註冊Servlet,攔截hystrix.stream

    @Bean

    public ServletRegistrationBean indexServletRegistration() {

        ServletRegistrationBean registration = new ServletRegistrationBean(new HystrixMetricsStreamServlet());

        registration.addUrlMappings("/hystrix.stream");

        return registration;

    }

 

3、下載tomcat7解壓縮

4、下載hystrix-dashboard的war包,

cp hystrix-dashboard-*.war apache-tomcat-7.*/webapps/hystrix-dashboard.war

5、下載turbin(整個集羣的監控)

下載並解壓縮

cp turbine-web/build/libs/turbine-web-*.war ./apache-tomcat-7.*/webapps/turbine.war

在/WEB-INF/classes下放置配置文件

config.properties

turbine.ConfigPropertyBasedDiscovery.default.instances=localhost

turbine.instanceUrlSuffix=:8081/hystrix.stream

6、啓動我們的服務

 

7、啓動tomcat中的hystrix dashboard和turbin

localhost:8080/hystrix-dashboard

http://localhost:8081/hystrix.stream,監控單個機器

http://localhost:8080/turbine/turbine.stream,監控整個集羣

 

以上爲配置在SpringBoot中集成Hystrix Dashboard,在SpringCloud中可以直接通過配置

<!---Hystrix Dashboard(只能看到單個應用內的服務信息) 的使用 可以單獨部署應用 http://localhost:8761/hystrix http://localhost:8888/hystrix.stream-->

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>

        </dependency>

 在啓動類中添加註解@EnableHystrixDashboard

uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif正在上傳…重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消

http://localhost:8761/hystrix

http://localhost:8888/hystrix.stream

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章