Hystrix 執行緒池與度量


如果應用程式基於多個服務,對服務的請求會佔用應用程式執行緒,若大量引用多個服務,而某(些)服務因故耗時,應用程式因這些服務請求而被佔用的執行緒增多,最後造成應用程式本身緩慢,提供的其他服務也連帶受到影響。

使用 Hystrix 時,預設每個被封裝的 Command,使用同一個執行緒池,預設最多有 10 個執行緒可使用,可藉由 hystrix.threadpool.default.coreSize 來調整,當執行緒用盡,請求就會失敗。

Hystrix 也可以為特定服務建立個別執行緒池,預設最多 10 個執行緒,對該服務的請求只佔用該執行緒池的執行緒,當執行緒用盡,後續請求會失敗,你可以設定執行緒池最多可用的執行緒,以及可等待的請求。例如:

@HystrixCommand(
    fallbackMethod = "fallbackMessages",
    threadPoolKey = "newestMessagesThreadPool",
    threadPoolProperties = {
            @HystrixProperty(name="coreSize", value = "20"),
            @HystrixProperty(name="maxQueueSize", value = "10")
    }
)
public List<Message> newestMessages(int n) {

threadPoolKey 指定了執行緒池名稱,coreSize 指定最多幾個執行緒,maxQueueSize 指定在執行緒用盡之後,最多可以有多少個請求處於等待,預設為 -1,使用 SynchronousQueue,也就是不等待而直接失敗,若設為大於 1 的值,會使用 LinkedBlockingQueue 來排隊請求。

在〈初識 Hystrix〉談過,Hystrix 提供速錯(Fail-Fast),可在請求服務的失敗率過高時(預設為 50%),直接斷開服務令請求失敗,而不是每次都等待逾時,斷開服務有助於緩解服務的負擔, 預設在 5 秒之後,才允許請求服務。

計算服務失敗率的方式是,根據 metrics.rollingStats.timeInMilliseconds 設定的時間間隔(預設值 10000 毫秒),計算請求服務的數量是否超過 circuitBreaker.requestVolumeThreshold 設定的次數(預設值 20 次),如果不是的話就允許請求,如果是的話,進一步計算失敗率是否超過 circuitBreaker.errorThresholdPercentage(預設值 50,也就是 50% 的失敗率),若是的話就斷開請求,若否的話就允許請求,斷開請求後的休眠時間由 circuitBreaker.sleepWindowInMilliseconds 決定(預設值 5000 毫秒),休眠之後請求若失敗,就再休眠一次。

一個設定的示範如下:

@HystrixCommand(
    fallbackMethod = "fallbackMessages",
    commandProperties = {
        @HystrixProperty(name="metrics.rollingStats.timeInMilliseconds", value = "15000"),
        @HystrixProperty(name="metrics.rollingStats.numBuckets", value = "5"),
        @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value = "25"),
        @HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value = "75"),
        @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value = "6000")
    },
    threadPoolKey = "newestMessagesThreadPool",
    threadPoolProperties = {
        @HystrixProperty(name="coreSize", value = "20"),
        @HystrixProperty(name="maxQueueSize", value = "10")
    }
)
public List<Message> newestMessages(int n) {

metrics.rollingStats.numBuckets 指定了 metrics.rollingStats.timeInMilliseconds 時間間隔內,用來收集統計數據的桶子(bucket)數量(預設值是 10),如果時間間隔為 10000 毫秒,桶子有 5 個,每個桶子會包含 2000 毫秒內的統計數據,metrics.rollingStats.timeInMilliseconds 必須能被 metrics.rollingStats.numBuckets 整除,否則會拋出例外,每個桶子中會包含成功、失敗、逾時、拒絕請求的次數。

預設情況下,Hystrix 會使用執行緒來來隔離請求,也建議這麼做,如果需要輕量級的隔離,可以設定 execution.isolation.strategy"SEMAPHORE"(預設值為 "THREAD"),採用信號的方式來限制最多可允許的請求,
基於信號雖然可以避免執行緒切換問題,然而,這意謂著與執行緒隔離相關的設定都無效,也就是說也不能採用斷路器的功能。

若真的要設定,一個範例是:

@HystrixCommand(
    fallbackMethod = "fallbackMessages",
    commandProperties = {
        @HystrixProperty(name="execution.isolation.strategy", value = "SEMAPHORE")
    }
)

如此一來,請求服務時不會另起執行緒,預設的請求數為 10,可透過 execution.isolation.semaphore.maxConcurrentRequests 來修改,每次請求服務,信號計數器值遞減,完成後遞增,在計數器為 0 時若還有請求,請求的執行緒就會被中斷,引發 InterruptedException