只有累積,沒有奇蹟

2019年6月8日 星期六

[NETCore] 使用 Polly 實現重試 (Retry) 策略

介紹
在開發時常常都會遇到串接其他 Server 或是第三方服務 API 的需求,當對方伺服器或是第三方服務發生問題時,或許可以在 Log 中看到回應的 HTTP 狀態碼是 404 (Not Found)、503(Service Unavailable)、504(Gateway Timeout)、500(Too Many Requests)...等當下無法正常執行的錯誤訊息,可能發生原因是因為對方 Server 不穩定,或是正在部署新版應用程式,造成在發送 Request 請求當下沒辦法回應請求內容,在過去可能會使用 while 加上 thread.sleep 達到,代碼可能長得像這樣
private static int retryCount = 3;
private static void doRetry(int second, Action action)
{
    while (true)
    {
        try
        {
            action(); // do something
            break; 
        }
        catch
        {
            if (--retryCount == 0)
                throw;
            Thread.Sleep(1000 * second);
        }
    }
}
這僅是最簡單版的重送機制還不包含 try cacht 處理,如果重送機制更複雜代碼會更多,或者希望用更好的方式來處理 故障 這件事,以上問題可以透過 Polly 類別庫來達到 故障處理 這件事。
Polly 是 .NET Foundation 認可的類別庫之一,在官方 GitHub 對於 Polly 介紹如下
Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.
Polly 是一套 .NET 處理瞬間故障的函式庫,提供開發人員用 Fluent API 方式處理服務瞬間故障的機制,並提供重試(Retry)、斷路(Circuit-breaker)、超時(Timeout)、隔離(Bulkhead Isolation)、緩存(Cache)、回退(Fallback) 等機制。此篇就針對 Polly 中最常用的重試 (Retry) 策略做簡單分享,若有問題歡迎一起討論。

安裝 Polly
為了方便大家可以更容易了解,會透過建立一個名為 PollyConsole 的 Console 專案作為範例,在建立完畢 Console 專案之後,下一步是在 Nuget 下載 Polly 套件,安裝方式也是與一般套件相同,開啟 nuget 後輸入 Polly 按下載,接著一直按下一步直到安裝完成
身邊有些朋友是不喜歡使用 GUI,也可以透過 Nuget Package Console 輸入下列指令
Install-Package Polly -Version 7.1.0
安裝完畢之後到專案檔底下確認是否有安裝成功
<ItemGroup>
  <PackageReference Include="Polly" Version="7.1.0" />
</ItemGroup>

改寫 while  
在文章的一開始就有提到可能發生的情境,這些故障情境可能是短暫發生的,對方服務(API)會在短時間修復,就可以使用 Retry 機制來重試,透過下列範例與說明,希望讓大家更容易了解

Mock Service
為了 Demo 方便這裡使用 mock REST API 來協助我們建立假的第三方服務,在這網站中可以自訂 Response 的 HTTP Status Code、Content Type 與 Body 內容,客製自己想要的 Response 內容後可以產生屬於自己的測試 REST API,舉例來說,這裡就建立  HTTP Status = 500  的流程機制,Body 為  This is 500 Erro ,按下 Generate HTTP Response 按鈕後得到 http://www.mocky.io/v2/5cfb4d9b3000006e080a8b0a,作為稍後測試使用。
Polly Retry
安裝完 Polly 以及建立好 mock API 之後,我們可以試著用 Polly 來改寫自己定義 while 的 code,使用 Polly API 改寫後代碼如下,當呼叫第三方服務  doMockHTTPrequest  發生異常 (HTTP StatusCode 不為 OK) 時,將會進行重次三次的動作,並將過程用 console.write 輸出在畫面上
static void Main(string[] args)
{
    Policy
   // 故障處理 : 要 handle 什麼樣的異常
   .Handle<HttpRequestException>()
   .OrResult<HttpResponseMessage>(result => result.StatusCode != HttpStatusCode.OK)
   // 重試策略 : 異常發生時要進行的重試次數及重試機制
   .Retry(3, onRetry: (exception, retryCount) =>
   {
       Console.WriteLine($"[App|Polly] : 呼叫 API 異常, 進行第 {retryCount} 次重試, Error :{exception.Result.StatusCode}");
   })
   // 要執行的任務
   .Execute(doMockHTTPRequest);

    Console.WriteLine("結束退出");
    Console.ReadKey();
}

static HttpResponseMessage doMockHTTPRequest()
{
    Console.WriteLine($"[App] {DateTime.Now.ToString(CultureInfo.InvariantCulture)}: 開始發送 Request");

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

    return result;
}
由於我們建立的 mock API 回傳定義 HTTP Status Code 為 500,因此發送 Request 請求會失敗,代碼執行後結果如下
透過 Polly 來實作重試機制成功,整體感覺使用 Fluent 方式來撰寫代碼也變得更為清晰,增加其可讀性,成功改寫完下一步就來介紹 Polly 使用方式。

注意 : 使用 async / await 會有 Thread lock 問題,造成原因及解法可以參考 Async/Await - Best Practices in Asynchronous Programming

Retry 重試策略 
Polly 主要結構可以分為

  • Handle<T> : 故障時的處理機制,或是指定要處理什麼樣的異常
  • Retry : 重試機制,定義發生故障時要重試的次數或指定工作
  • Execute : 要執行的任務

  • Handle
    故障時的處理機制,或是指定要處理什麼樣的異常,可以根據需求來定義所要故障處理的 exception,如果希望能夠同時 handle 多個例外可以使用  or<T>  來實現
    // Single exception type
    Policy.Handle<HttpRequestException>()
    
    // Multiple exception types
    Policy
      .Handle<HttpRequestException>()
      .Or<OperationCanceledException>()
    在 Polly 中也提供 API 依據回傳的內容 (return results) 來進行故障處理
    // 回傳內容為 404 
    Policy
      .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)
    
    // 回傳內容為 500 & 502
    Policy
      .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
      .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)
    
    在代碼中定義了要處理什麼樣的故障,下一步就是要針對故障做怎麼樣處理策略,Polly 在重試策略中提供  Retry  與   Wait and Retry  兩種重試 API,分別如下

    Retry
    提供指定重試次數、每次重試時處理等相關 API,廢話不多說直接上 Code
    // 重次 : 預設重試一次
    Policy
      .Handle<SomeExceptionType>()
      .Retry()
    
    // 重次多次
    Policy
      .Handle<SomeExceptionType>()
      .Retry(7)
    
    // 在每次重試時做處理,
    Policy
        .Handle<SomeExceptionType>()
        .Retry(3, onRetry: (exception, retryCount) =>
        {
            // do something 
        });
    如果回傳內容很重要,Polly 也提供持續重試的 API 互相傷害的概念(誤)
    Policy
      .Handle<SomeExceptionType>()
      .RetryForever(onRetry: (exception, context) =>
      {
            // do something       
      });
    

    Wait and Retry
    如果想要自己定義重試的頻率與時間,而非固定的1秒呼叫一次的話,可以使用 wait and retry 來自定義呼叫的時間區間,讓重試機制更有彈性 
    Policy
      .Handle<SomeExceptionType>()
      .WaitAndRetry(new[]
      {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(3)
      });
    輸出如下
    在一些情境底中如果短時間的重試可能還是無法正常,也可以考慮使用函數來計算重試的時間,下列範例重試時間為 2、4、8、16、32 秒,用 2 的倍數方式來做為重試時間 
    Policy
      .Handle<SomeExceptionType>()
      .WaitAndRetry(5, retryAttempt => 
     TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 
      );

    Retry After
    有些系統或是應用程式有限流機制,例如之前在 ASP.NET Core 介紹過的 ASP.NET Core 限流框架 AspNetCoreRateLimit,可以設定某時間內重試次數達到定義的水位時,系統將會阻擋 Request 無法使用,透過 Chrome 開發者工具可以看到回傳的 HTTP StatusCode 如下
    429 Too Many Requests
    此時系統將會回應 http status code 為 429,並在 Header 中定義 Retry After 訊息
    也就是說是無法使用 Retry 重送,只能透過 Wait and Retry 來達到此需求,在官網中也有提供 Retry after 的範例說明 : 傳送門。 

    Polly Retry flow
    上面介紹了兩種 Retry 的 API 與使用方式,在官網中也有很貼心的提供 Retry 的流程機制

    感想
    原本想要介紹 Polly 的完整功能,但由於時間以及避免這篇太長因此決定先介紹在 Polly 中最常用到的 重試策略,在官方的 GitHub 也提供了相當詳細的 API 說明與 sample code 範例可以使用,這邊僅是擷取自己覺得重要與常用的部分,若想了解更多非常推薦到官網了解,之後若有更多時間會在介紹 Polly 其他功能與範例 (希望有空 XDD),謝謝

    參考
    HTTP Status Code
    ASP.NET Core 微服务初探[2]:熔断降级之Polly
    NET 开源项目 Polly 介绍
    App-vNext/Polly



    0 意見:

    張貼留言

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

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com