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屬性的配置,一般執行緒被回收前的存活時間應該小於最大超時時間,即在請求時間超出超時時間之前,執行緒應該都處於存活,並處理完所有的請求。