關於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

輸出結果:

  • 640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

  • 主線程創建HystrixCommand並執行,Hystrix創建子線程發起http請求,5秒後收到響應。最後主線程收到正確響應結果。

測試場景2:RestTemplate超時,HystrixCommand沒有超時

參數設置:

  • RestTemplate超時時間<接口響應時間(5s),Hystrix超時時間>接口響應時間(5s)

  • HTTP_TIMEOUT設置爲3s,HYSTRIX_TIMEOUT設置爲10s

輸出結果:

  • 640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

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

測試場景3:RestTemplate沒有超時,HystrixCommand超時

參數設置:

  • RestTemplate超時時間>接口響應時間(5s),Hystrix超時時間<接口響應時間(5s)

  • HTTP_TIMEOUT設置爲10s,HYSTRIX_TIMEOUT設置爲3s

輸出結果:

  • 640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

  • 整個流程爲:主線程創建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.未超時,正常返回

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

2.任務請求超時

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

3.HystrixCommand超時

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


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