只有累積,沒有奇蹟

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 達到,代碼可能長得像這樣
  1. private static int retryCount = 3;
  2. private static void doRetry(int second, Action action)
  3. {
  4. while (true)
  5. {
  6. try
  7. {
  8. action(); // do something
  9. break;
  10. }
  11. catch
  12. {
  13. if (--retryCount == 0)
  14. throw;
  15. Thread.Sleep(1000 * second);
  16. }
  17. }
  18. }
這僅是最簡單版的重送機制還不包含 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 輸入下列指令
  1. Install-Package Polly -Version 7.1.0
安裝完畢之後到專案檔底下確認是否有安裝成功
  1. <ItemGroup>
  2. <PackageReference Include="Polly" Version="7.1.0" />
  3. </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 輸出在畫面上
  1. static void Main(string[] args)
  2. {
  3. Policy
  4. // 故障處理 : 要 handle 什麼樣的異常
  5. .Handle<HttpRequestException>()
  6. .OrResult<HttpResponseMessage>(result => result.StatusCode != HttpStatusCode.OK)
  7. // 重試策略 : 異常發生時要進行的重試次數及重試機制
  8. .Retry(3, onRetry: (exception, retryCount) =>
  9. {
  10. Console.WriteLine($"[App|Polly] : 呼叫 API 異常, 進行第 {retryCount} 次重試, Error :{exception.Result.StatusCode}");
  11. })
  12. // 要執行的任務
  13. .Execute(doMockHTTPRequest);
  14.  
  15. Console.WriteLine("結束退出");
  16. Console.ReadKey();
  17. }
  18.  
  19. static HttpResponseMessage doMockHTTPRequest()
  20. {
  21. Console.WriteLine($"[App] {DateTime.Now.ToString(CultureInfo.InvariantCulture)}: 開始發送 Request");
  22.  
  23. HttpResponseMessage result;
  24. using(HttpClient client = new HttpClient())
  25. {
  26. result = client.GetAsync("http://www.mocky.io/v2/5cfb4d9b3000006e080a8b0a").Result;
  27. }
  28.  
  29. return result;
  30. }
由於我們建立的 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>  來實現
    1. // Single exception type
    2. Policy.Handle<HttpRequestException>()
    3.  
    4. // Multiple exception types
    5. Policy
    6. .Handle<HttpRequestException>()
    7. .Or<OperationCanceledException>()
    在 Polly 中也提供 API 依據回傳的內容 (return results) 來進行故障處理
    1. // 回傳內容為 404
    2. Policy
    3. .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)
    4.  
    5. // 回傳內容為 500 & 502
    6. Policy
    7. .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
    8. .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)
    在代碼中定義了要處理什麼樣的故障,下一步就是要針對故障做怎麼樣處理策略,Polly 在重試策略中提供  Retry  與   Wait and Retry  兩種重試 API,分別如下

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

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

    Retry After
    有些系統或是應用程式有限流機制,例如之前在 ASP.NET Core 介紹過的 ASP.NET Core 限流框架 AspNetCoreRateLimit,可以設定某時間內重試次數達到定義的水位時,系統將會阻擋 Request 無法使用,透過 Chrome 開發者工具可以看到回傳的 HTTP StatusCode 如下
    1. 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



    Related Posts:

    • [Docker] Docker for windows 初體驗前言 Docker 是一個 open source 專案,誕生於 2013 年初,Docker 屬於 Linux 容器的一種封裝,提供了簡單且易用的容器接口,在 2016 推出 Docker for windows 版本,相信大家或多或少都聽過甚至已經有在使用,自己也接觸過 docker 一小段時間,但都屬於玩票性質未好好研究,或許在新筆電重新安裝的時候是一個好契機可以靜下心來深入研究,以下簡單介紹 Docker 在 Windows 上的安裝流程… Read More
    • [NETCore] 在 ASP.NET Core Web API 中使用 Serilog 前言 近期專案都在嘗試 Serilog,對於使用上有點小小心得,因此整理這篇文章分享 Serilog 在 ASP.NET Core API 上的使用與設定,也接續之前 Serilog 的系列介紹文 : 傳送門 的實際應用心得,希望可以幫到有需要的朋友,若有問題或是錯誤的地方歡迎提出來一起討論或是給予指導。 ASP.NET Core logging 首先先建立新的 ASP.NET Core Web Applicat… Read More
    • [NETCore] 結構化日誌 Serilog - Events Types 和 Enrichment 前言 前兩篇分別介紹了關於 Serilog 的基礎應用與設定,這篇就來針對事件類型 Event Type 與 介紹幾個常用的 Enricher,若有問題或是錯誤的地方歡迎提出來一起討論。 Event Type 結構化日誌的好處是可以清楚的分辨"每一次"紀錄的事件,舉例來說下列簡單的代碼是透過 Serilog 寫入log 到 Console 與 File 檔案,紀錄內容是 3 筆資料 Log.Logger = new LoggerCo… Read More
    • [NETCore] ASP.NET Core 3.0 Preview 體驗 前言 ASP.NET Core 3.0 preview 5 已經推出,在 3.0 最大的變化是對 windows desktop application 的支援, 以及支援 .NET Standard 2.1 和可以在 Visual Studio 2019 上使用 C# 8.0 功能,這篇文章紀錄安裝 ASP.NET Core 3.0 SDK 以及 升級 Visual Studio 2019 Update,若有問題或是錯誤的地方歡迎提出來一起討… Read More
    • [NETCore] ASP.NET Core 啟動異常 - HTTP Error 500.30 - ANCM In-Process Start Failure 問題  在開發專案時跳出異常訊息,錯誤訊息為  HTTP Error 500.30 - ANCM In-Process Start Failure ,這篇就針對此案例作簡單紀錄與分享,若是有不清楚或是錯誤的地方歡迎討論予糾正。 解決方法  廢話不多說,先看案發現場的錯誤畫面 執行異常的程式代碼,看起來很單純的代碼 public static void Main(s… Read More

    0 意見:

    張貼留言

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

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com