本文主要對使用 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,這時還要考慮使用不同的執行緒池防止任務互相排隊。
參考文件
- 《spring cloud 微服務實戰》
- Hystrx權威指南--Hystrix的執行緒池解析