1. 程式人生 > >Hystrix中threadPoolProperties執行緒池各個屬性舉例測試

Hystrix中threadPoolProperties執行緒池各個屬性舉例測試

目前的工作場景是:

在一個專案中需要呼叫外部介面,此介面一次只能處理8個請求,多於8個請求過來,nginx會為了保護介面直接踢回請求(返回500null錯誤),而在本專案中使用了訊息佇列機制,所以有可能會一次從訊息佇列中消費多條資料,這時候就會有個別請求還沒有呼叫外部介面直接返回了500錯誤。

這時候就需要考慮對專案中呼叫介面的方法進行核心執行緒控制,這就涉及到hystrix的核心執行緒數概念。

編寫程式碼模擬外部介面

這是一個service方法:

@HystrixCommand(commandKey = "testCoreSizeCommand",groupKey = "testGroup",fallbackMethod = "TimeOutFallBack",
    threadPoolProperties = {
            @HystrixProperty(name = "coreSize",value = "2"),
            @HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize",value="true"),
            @HystrixProperty(name = "maximumSize",value="2"),
            @HystrixProperty(name = "maxQueueSize",value="2")
    },
    commandProperties = {
            @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "16000" )
    })
    @Override
    public String coreSizeTest() throws InterruptedException {

        Thread.sleep(5000);
        
        System.out.println("進來一次");
        
        return "coreSizeTest finished";
    }
    
    public String TimeOutFallBack(){
        
        return "降級sorry, the request is timeout";
    }

為了更直觀的感受,每個請求會sleep 5秒之後返回。

編寫一個controller,呼叫service:

@RequestMapping(value="test_coresize",method=RequestMethod.GET)
    public String getHystrixSizeTest() throws InterruptedException{
        
        String test = hystrixService.coreSizeTest();
        
        return test;
    }

這樣通過傳送請求訪問localhost:8080/test_coresize就可以對介面進行呼叫。

三個執行緒同時訪問介面

public class MyThred extends Thread{

    @Override
    public void run() {

        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();

        headers.add("Content-Type", "application/json; charset=UTF-8");
        headers.add("Accept", "*/*");

        HttpEntity<String> requestEntity = new HttpEntity<>("", headers);

        ResponseEntity<String> exchange = restTemplate.exchange("http://localhost:8080/test_coresize", HttpMethod.GET, requestEntity,String.class);

        String body = exchange.getBody();
        
        System.out.println("請求結果:"+body);
        
    }
}

public static void main(String[] args) {
        
        MyThred myThred1 = new MyThred();
        MyThred myThred2 = new MyThred();
        MyThred myThred3 = new MyThred();
        myThred1.start();
        myThred2.start();
        myThred3.start();
    }

測試過程中各種情況分析

1. 設定coresize=2,maximumSize=2,未定義降級方法

coresize即核心執行緒數,即可以同時接受的請求數,maximumSize最大執行緒數,即超過核心執行緒數之後,多餘的執行緒會處於空閒狀態,等核心執行緒使用它。開啟maximumSize需要設定allowMaximumSizeToDivergeFromCoreSize為true。

這裡我設定兩個屬性都是2,即一次只能接收兩個請求,我最初的想法是既然只能接受兩個請求,那我傳送三個請求,那是不是應該第三個請求等待2個請求處理完了之後再進行處理。一次請求需要5秒時間,同時可以處理兩個請求,那麼三個請求應該是10s後全部返回給我。

但是現實很快就給了我狠狠的一巴掌,兩個請求成功,第三個請求返回錯誤500 null。原來:超過了最大執行緒數之後的請求會被hystrix降級處理,即呼叫hystrxi中的fallback屬性指定的降級方法。如果沒有指定降級方法,則會預設返回null。這就是為什麼我會有一個請求返回了500的錯誤。

2. 設定coresize=2,maximumSize=2,定義降級方法:返回資訊降級sorry, the request is timeout

在定義了降級方法之後,超過最大執行緒的請求會呼叫降級方法,這時候就沒有出現報錯,而是列印了降級方法返回的資訊。

3. 設定coresize=2,maximumSize=4,未定義降級方法

由上面的例子我們知道,執行緒池核心心執行緒數目都在忙碌,再有新的請求到達時,執行緒池容量可以被擴充為到最大數量,等到執行緒池空閒後,多於核心數量的執行緒還會被回收。

那我設定了maximumSize=4,這時候有3個請求過來的時候,執行緒池會被擴大到最大數量,最大容量數大於請求數,不會呼叫降級方法。這時候比較疑惑的點是三個請求一起處理,還是先處理兩個,再處理第三個呢。

從測試結果上看:三個請求是一起返回過來的。那這就說明:請求數小於最大執行緒數,卻大於核心執行緒數的時候,會一起處理所有的請求,當所有請求處理完畢的時候,會將多餘核心數量的執行緒釋放。

4. 設定coresize=2,maxQueueSize=2,maximumSize=4,未定義降級方法

maxQueueSize指的是作業佇列的最大值,核心執行緒池內的執行緒都在忙碌時,會將作業暫時存放在此佇列內,但超出此佇列的請求依然會被拒絕。預設值為 -1,設定為此值時,佇列會使用 SynchronousQueue,此時其 size 為0,Hystrix 不會向佇列記憶體放作業。即預設hystrix是不會使用佇列的。

那我現在配置了maxQueueSize=2,那麼我傳送三個請求的時候,就應該是有2個請求在處理,有一個請求在作業佇列中等待處理。最後請求返回的時間也應該不是同步的。果然,有兩個請求先返回回來,還有一個請求在5秒後返回。

測試結果總結

  • 如果請求量少,達不到 coreSize,通常會使用核心執行緒來執行任務。

  • 如果設定了 maxQueueSize,當請求數超過了 coreSize, 通常會把請求放到 queue 裡,待核心執行緒有空閒時消費。

  • 如果 queue 長度無法儲存請求,則會建立新執行緒執行直到達到 maximumSize 最大執行緒數,多出核心執行緒數的執行緒會在空閒時回收。

所以正確的配置方式是根據顯示場景需要進行設定:coresize<maxQueueSize<maximumSize

需要注意的是threadPoolProperties還有兩個屬性:

  • queueSizeRejectionThreshold:由於 maxQueueSize 值線上程池被建立後就固定了大小,如果需要動態修改佇列長度的話可以設定此值,即使佇列未滿,佇列內作業達到此值時同樣會拒絕請求。此值預設是 5,所以有時候只設置了 maxQueueSize 也不會起作用。

  • keepAliveTimeMinutes:由上面的 maximumSize,我們知道,執行緒池核心心執行緒數目都在忙碌,再有新的請求到達時,執行緒池容量可以被擴充為到最大數量,等到執行緒池空閒後,多於核心數量的執行緒還會被回收,此值指定了執行緒被回收前的存活時間,預設為 2,即兩分鐘。

在實際的使用過程中,還應該考慮最大超時時間timeoutInMilliseconds與keepAliveTimeMinutes屬性的配置,一般執行緒被回收前的存活時間應該小於最大超時時間,即在請求時間超出超時時間之前,執行緒應該都處於存活,並處理完所有的請求。