Hystrix Thread Pool 解析

鄒華健可愛多發表於2018-10-01

本文主要對使用 Hystrix 執行緒隔離 時的一些問題進行解析

一、Hystrix 執行緒隔離概述

相信大家對於 Docker 的“艙壁模式”並不陌生,Hystrix 則是使用了該模式現實了執行緒池的隔離,我們可以為每個依賴服務建立一個獨立的執行緒池,每個執行緒池之間不會互相影響。每個 HystrixCommand 都會執行在相對應的執行緒池中,這樣我們就能針對不同的服務可以很好的控制 Command 的併發量。
Hystrix 執行緒池預設是以相同命令組區分,如果需要自定義執行緒隔離,可使用 ThreadPoolKey 引數指定執行緒池的劃分。Hystrix 原始碼中是將 ThreadPoolKey 和 HystrixThreadPool 儲存在一個 ConcurrentHashMap 中,如果我們想將某些命令放在同一個執行緒池中執行時,我們僅需要指定相同的 ThreadPoolKey 即可。

// HystrixThreadPool 原始碼  

/*
 * Use the String from HystrixThreadPoolKey.name() instead of the HystrixThreadPoolKey instance as it's just an interface and we can't ensure the object
 * we receive implements hashcode/equals correctly and do not want the default hashcode/equals which would create a new threadpool for every object we get even if the name is the same
 */
final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();
 
static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
    // get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work
    String key = threadPoolKey.name();
    
    // this should find it for all but the first time
    HystrixThreadPool previouslyCached = threadPools.get(key);
    if (previouslyCached != null) {
        return previouslyCached;
    }
    
    // if we get here this is the first time so we need to initialize
    synchronized (HystrixThreadPool.class) {
        if (!threadPools.containsKey(key)) {
            threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
        }
    }
    return threadPools.get(key);
}
複製程式碼

二、HystrixThreadPool 引數解析

原生 API 引數設定

// Demo
public CommonHystrixCommand() {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("defaultGroupKey"))
            //設定當前command的key,預設值為當前類名
            .andCommandKey(HystrixCommandKey.Factory.asKey("defaultCommandKey"))
            // 超時時間,單位預設1000ms
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                    .withExecutionTimeoutInMilliseconds(500)
            )
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("defaultThreadPool"))
            .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                    // 設定核心執行緒池大小,預設為10
                    .withCoreSize(10)
                    // 設定此項為 true,最大執行緒池數才生效,預設false
                    .withAllowMaximumSizeToDivergeFromCoreSize(true)
                    // 最大執行緒池數,預設10
                    .withMaximumSize(10)
                    // 設定保持存活的時間,單位是分鐘,預設是1
                    .withKeepAliveTimeMinutes(1)
                    // 設定當前執行緒池的等待佇列的大小,預設為5
                    .withMaxQueueSize(5)
                    // queue 拒絕大小, 即使maxQueueSize沒有達到,達到queueSizeRejectionThreshold該值後,請求也會被拒絕。
                    .withQueueSizeRejectionThreshold(5)
            ));
}
複製程式碼

三、getFallback() 到底在哪個執行緒池中執行?

雖然 getFallback() 和 run() 是共用一個執行緒池,但是在一些情況下,getFallback() 用的是其他執行緒池。 下面來看下各種情況下程式碼是執行在哪個執行緒中的:

ThreadPool模式下

  • 超時呼叫 getFallback:HystrixTimer執行緒
  • 執行緒池佇列滿呼叫 getFallback:主執行緒
  • Command出錯呼叫 getFallback:自定義Command執行緒池

Semaphore模式下

  • 超時呼叫 getFallback:Timer執行緒
  • 併發數滿呼叫 getFallback:主執行緒
  • Command出錯呼叫 getFallback:Command執行緒池

在使用Hystrix時要注意各個程式碼是執行在哪個執行緒中的,防止在不必要的地方有阻塞的呼叫,如在fallback中如果有阻塞耗時操作,那麼在佇列滿時會導致主執行緒阻塞,可以考慮在Fallback中再呼叫新Command,這時還要考慮使用不同的執行緒池防止任務互相排隊。



參考文件

相關文章