只有累積,沒有奇蹟

2019年6月11日 星期二

[NETCore] Polly 的超時 TimeOut 和 Wrap 策略

介紹
Polly 是一套 .NET 處理瞬間故障的函式庫,提供開發人員用 Fluent API 方式及 Thread Safe 處理服務瞬間故障的策略,並提供重試(Retry)、斷路(Circuit-breaker)、超時(Timeout)、隔離(Bulkhead Isolation)、緩存(Cache)、回退(Fallback) 等機制,在上一篇 [NETCore] 使用 Polly 實現重試 (Retry) 策略 分享了 Retry 機制,這一篇就來介紹 Polly 中的 Timeout 策略,若有問題歡迎一起討論。

Timeout 策略
Polly 安裝與基本語法不再重複介紹,如果有興趣可以參考上一篇文章 : 傳送門。超時在開發中很常遇到,一般在發送 Request 到其他服務或是第三方伺服器時,都會設定 timeout 時間以避免使用者等待過久,過去在使用 httpClient 與 httpWebRequest 中可以透過 timeout 來調整等待設定,在 Polly 中設定 timeout 方式也是使用  Policy.Timeout  API 來設定逾時時間,簡單範例如下
// 設定逾時時間為 10 秒
Policy.Timeout(10);
// async
Policy.TimeoutAsync(10);
Polly 在 Timeout API 也提供多載方法方便開發者設定,同樣也可以使用 Action 方法
Policy.Timeout(10, (context, timespan, task) =>
{
    // do something , ex : log.info
});
以下透過簡單範例代碼示範在 Polly 設定 Timeout 策略,在範例代碼中的 doTimeOutHTTPRequest 主要功能使用 httpclient 呼叫其他服務,為了 Demo 方便設定 timeout 時間為 3ms 讓請求出現 time exception,套用 Polly timeout 策略設定 timeout 為 1 ms,並在發生 timeout exception 時顯示 log 資訊
static void Main(string[] args)
{
    Policy
        .Timeout(TimeSpan.FromMilliseconds(1), onTimeout: (context, timespan, task) =>
        {        
            Console.WriteLine($"{context.PolicyKey} : execution timed out after {timespan} seconds.");
        })
        .Execute(doTimeOutHTTPRequest);
}

static string doTimeOutHTTPRequest()
{
    Console.WriteLine($"開始發送 Request");

    HttpResponseMessage response;
    using (HttpClient client = new HttpClient())
    {
        client.Timeout = TimeSpan.FromMilliseconds(3);
        response = client.GetAsync(" http://www.mocky.io/v2/5cfed9a23200004f0045f284").Result;
    }

    return response.Content.ReadAsStringAsync().Result; 
}
執行後顯示內容如下
如果開啟 debug mode 可以看到當 1ms 沒有完成任務,會觸發 TimeoutRejectedException 異常
另外,在 Timeout 策略中可以設定 TimeoutStrategy,分為 Optimistic 與 Pessimistic 兩種設定值, 預設為 Optimistic 也就是支援發送取消 (CancellationToken),TimeoutStrategy.Optimistic 中設定 CancellationToken 代碼如下
Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Optimistic);
HttpResponseMessage httpResponse = await timeoutPolicy
    .ExecuteAsync(
        async ct => await httpClient.GetAsync(requestEndpoint, ct), 
        CancellationToken.None 
    );
如果對於 TimeoutStrategy 想解瞭更多的話,可以到官方 GitHub Timeout 文章中有針對 Optimistic Timeout 與 Pessimistic Timeout 使用上更詳細的說明與範例代碼 : 傳送門

Wrap 策略
Polly 支援多個故障時的策略機制,可以因應需求來組合其故障處理策略,這也是 Polly 彈性的設計,wrap 概念就像是策略包,可以將不同的故障策略包在一起設定為遇到故障時的處理機制方法,在組合時以下語法都支援
// Execute
fallback.Execute(() => waitAndRetry.Execute(() => breaker.Execute(action)));

// Policy.Wrap
Policy
  .Handle<SomeExceptionType>()
  .Retry(7)

// equivalently sample 1
fallback.Wrap(waitAndRetry.Wrap(breaker)).Execute(action);

// equivalently sample 2
fallback.Wrap(waitAndRetry).Wrap(breaker).Execute(action);
廢話不多說直接舉範例來說明,上一篇介紹了 Retry 策略這篇介紹了 Timeout 策略,我們就來結合兩個做為一個策略包,並在每個策略執行時將內容輸出在 console 上面
static async Task Main(string[] args)
{
    var timeoutPolicys = Policy
        .Timeout(TimeSpan.FromMilliseconds(1),
            onTimeout: (context, timespan, task) =>
            {
                Console.WriteLine($"{context.PolicyKey} : execution timed out after {timespan} seconds.");
            });

    RetryPolicy waitAndRetryPolicy = Policy
        .Handle<Exception>()
        .Retry(3,
            onRetry: (exception, retryCount) =>
            {
                Console.WriteLine($"[Polly retry] : 呼叫 API 異常, 進行第 {retryCount} 次重試");
            });

    FallbackPolicy<String> fallbackForTimeout = Policy<String>
        .Handle<TimeoutRejectedException>()
        .Fallback(
            fallbackValue: "Please try again later [Fallback for timeout]",
            onFallback: b => { Console.WriteLine($"這個請求超時了耶"); }
        );

    FallbackPolicy<String> fallbackForAnyException = Policy<String>
        .Handle<Exception>()
        .Fallback(
            fallbackAction: () => { return "Please try again later [Fallback for any exception]"; },
            onFallback: e => { Console.WriteLine($"[Polly fallback] : 重試失敗, say goodbye"); }
        );

    PolicyWrap<String> policyWrap = fallbackForAnyException.Wrap(fallbackForTimeout).Wrap(waitAndRetryPolicy)
        .Wrap(timeoutPolicys);
    policyWrap.Execute(() => doMockHTTPRequest());
}

static string doMockHTTPRequest()
{
    Console.WriteLine($"開始發送 Request");

    HttpResponseMessage result;
    using (HttpClient client = new HttpClient())
    {
        client.Timeout = TimeSpan.FromMilliseconds(3);
        result = client.GetAsync("http://www.mocky.io/v2/5cfb4d9b3000006e080a8b0a").Result;
    }

    return result.Content.ReadAsStringAsync().Result;
}
執行之後輸出如下
同樣的在官網中也有提供 wrap 的範例說明 : 傳送門。 

感想
在嘗試 wrap 的過程中花了不少時間跟踩雷,Handle 定義若是指定錯誤就會不會進到指定的故障策略中,舉例來說當 Timeout 時丟出的錯誤訊息為TimeoutRejectedException,若是沒有指定到此 exception 就不會進到 retry 機制當中,或是放大絕招直接handle exeption,在 Polly 的 wrap 中使用上還是有不少眉眉角角的細節要了解,也謝謝強者同事吉米斯提醒了重要的觀念才明白,日後若有比較值得分享的應用也會在PO出來,謝謝

參考
App-vNext/Polly


0 意見:

張貼留言

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com