關於Hystrix超時機制和線程狀態的測試觀察和個人理解

一般在使用Hystrix時,大部分情況下都是直接基於SpringCloud的相關注解來完成請求調用的。而我們有個項目是手動創建HystrixCommand來包裹RestTemplate發起請求的。但是在服務運行過程中,發現一個情況,就是當HystrixCommand超時返回fallback結果後,RestTemplate請求過程還沒有結束,導致線程池佔用較多。

這裏通過一個簡單的測試,對RestTemplate和HystrixCommand設置不同的超時時間,來觀察在HystrixCommand執行過程中的細節。

測試觀察

模擬外部服務:

創建一個springboot服務,提供一個接口:等待5秒後返回數據

@RestController
public class DataController {

	@RequestMapping("queryData")
	public String queryData() {
		// 等待5s後,返回隨機字符串
		long sleepTime = 5000;
		try {
			Thread.sleep(sleepTime);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return UUID.randomUUID().toString();
	}
}
測試代碼:
public class HystrixCommandTest {

	private static final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");

	// http請求超時時間
	private static final int HTTP_TIMEOUT = 10000;
	// hystrix超時時間
	private static final int HYSTRIX_TIMEOUT = 10000;

	private RestTemplate restTemplate;

	@Before
	public void init() {
		HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
		httpRequestFactory.setReadTimeout(HTTP_TIMEOUT);
		restTemplate = new RestTemplate(httpRequestFactory);
	}

	@Test
	public void test() {
		// 創建HystrixCommand.Setter
		HystrixCommandProperties.Setter propSetter = HystrixCommandProperties.Setter()
				.withExecutionTimeoutEnabled(true)	//開啓超時機制
				.withExecutionTimeoutInMilliseconds(HYSTRIX_TIMEOUT)	//設置超時時間
				.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)	//線程隔離
				.withExecutionIsolationThreadInterruptOnTimeout(true);	//這裏設置超時中斷線程,但其實沒有實際效果
		HystrixCommand.Setter setter = HystrixCommand.Setter
				.withGroupKey(HystrixCommandGroupKey.Factory.asKey("queryData"))
				.andCommandPropertiesDefaults(propSetter);

		// 通過Setter創建創建HystrixCommand
		HystrixCommand<String> hystrixCommand = new HystrixCommand<String>(setter) {
			@Override
			protected String run() throws Exception {
				// 發起http請求
				print("send request");
				String result = restTemplate.getForObject("http://127.0.0.1:9001/queryData", String.class);
				print("get response");
				return result;
			}

			@Override
			protected String getFallback() {
				print("fallback");
				return null;
			}
		};
		print("execute command");
		// 執行HystrixCommand
		String result = hystrixCommand.execute();
		print("get result=" + result);
		// 阻塞main線程,防止程序終止
		while (true) {
		}
	}

	private void print(String msg) {
		System.out.println(df.format(new Date()) + " [" + Thread.currentThread().getName() + "]:" + msg);
	}
}
測試場景1:RestTemplate和HystrixCommand都沒有超時

參數設置:

  • RestTemplate超時時間>接口響應時間(5s),Hystrix超時時間>接口響應時間(5s)
  • HTTP_TIMEOUT和HYSTRIX_TIMEOUT都設置爲10s

輸出結果:

  • 主線程創建HystrixCommand並執行,Hystrix創建子線程發起http請求,5秒後收到響應。最後主線程收到正確響應結果。
測試場景2:RestTemplate超時,HystrixCommand沒有超時

參數設置:

  • RestTemplate超時時間<接口響應時間(5s),Hystrix超時時間>接口響應時間(5s)
  • HTTP_TIMEOUT設置爲3s,HYSTRIX_TIMEOUT設置爲10s

輸出結果:

  • 整個流程爲:主線程創建HystrixCommand並執行,Hystrix創建子線程發起http請求,3秒後http請求超時,進入fallback方法。最後主線程收到結果爲null。
測試場景3:RestTemplate沒有超時,HystrixCommand超時

參數設置:

  • RestTemplate超時時間>接口響應時間(5s),Hystrix超時時間<接口響應時間(5s)
  • HTTP_TIMEOUT設置爲10s,HYSTRIX_TIMEOUT設置爲3s

輸出結果:

  • 整個流程爲:主線程創建HystrixCommand並執行,Hystrix創建子線程發起http請求,3秒後Hystrix超時,HystrixTimer線程調用fallback方法。最後主線程收到結果爲null。
  • 但注意,這裏main方法收到返回結果後,發起http請求的線程在5s後還是收到了請求。也就是說,這裏即使HystrixCommand超時結束了,其實際發起請求的子線程並不會結束,即使設置了withExecutionIsolationThreadInterruptOnTimeout(true)也沒有用。

底層機制

我參考芋道源碼的Hystrix源碼解析相關文章(跳轉鏈接),並閱讀了Hystrix部分源碼後瞭解到:

HystrixCommand執行過程中,有兩個線程,一個是HystrixCommand任務執行線程,一個是等着給HystrixCommand判定超時的線程(HystrixTimer)。當其中一個線程完成自己的邏輯時,會嘗試將HystrixCommand的狀態置換(CAS),只要任何一個線程對HystrixCommand打上標就意味着超時判定結束。

  • 如果任務執行線程先完成,就會將status設置爲completed,超時監聽線程在到達超時時間時,發現status已經被標記爲完成狀態,直接結束。(對應上面的場景1和2)
  • 如果超時監聽線程先到達超時時間點,就會將status設置爲timeout,此時HystrixCommand會執行fallback中的流程,同時任務執行線程依舊在運行,直到其流程終止。(對應上面的場景3)

流程梳理

結合底層的原理,對上述三種場景簡單地畫一下流程圖,便於理解:

1.未超時,正常返回

2.任務請求超時

3.HystrixCommand超時

以上均爲個人理解,水平有限,如果文中有錯誤的地方,歡迎指出,希望能多與各位討論交流。
參考資料:竽道源碼 - Hystrix 源碼解析 —— 命令執行(三)之執行超時

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章