在開發時常常都會遇到串接其他 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) 策略做簡單分享,若有問題歡迎一起討論。
為了方便大家可以更容易了解,會透過建立一個名為 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 請求會失敗,代碼執行後結果如下
注意 : 使用 async / await 會有 Thread lock 問題,造成原因及解法可以參考 Async/Await - Best Practices in Asynchronous Programming
Retry 重試策略
Polly 主要結構可以分為
Handle
故障時的處理機制,或是指定要處理什麼樣的異常,可以根據需求來定義所要故障處理的 exception,如果希望能夠同時 handle 多個例外可以使用 or<T> 來實現
Retry
提供指定重試次數、每次重試時處理等相關 API,廢話不多說直接上 Code
互相傷害的概念(誤)
Wait and Retry
如果想要自己定義重試的頻率與時間,而非固定的1秒呼叫一次的話,可以使用 wait and retry 來自定義呼叫的時間區間,讓重試機制更有彈性
在一些情境底中如果短時間的重試可能還是無法正常,也可以考慮使用函數來計算重試的時間,下列範例重試時間為 2、4、8、16、32 秒,用 2 的倍數方式來做為重試時間
Retry After
有些系統或是應用程式有限流機制,例如之前在 ASP.NET Core 介紹過的 ASP.NET Core 限流框架 AspNetCoreRateLimit,可以設定某時間內重試次數達到定義的水位時,系統將會阻擋 Request 無法使用,透過 Chrome 開發者工具可以看到回傳的 HTTP StatusCode 如下
也就是說是無法使用 Retry 重送,只能透過 Wait and Retry 來達到此需求,在官網中也有提供 Retry after 的範例說明 : 傳送門。
Polly Retry flow
上面介紹了兩種 Retry 的 API 與使用方式,在官網中也有很貼心的提供 Retry 的流程機制
// 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
原本想要介紹 Polly 的完整功能,但由於時間以及避免這篇太長因此決定先介紹在 Polly 中最常用到的 重試策略,在官方的 GitHub 也提供了相當詳細的 API 說明與 sample code 範例可以使用,這邊僅是擷取自己覺得重要與常用的部分,若想了解更多非常推薦到官網了解,之後若有更多時間會在介紹 Polly 其他功能與範例 (希望有空 XDD),謝謝
參考
HTTP Status Code
ASP.NET Core 微服务初探[2]:熔断降级之Polly
NET 开源项目 Polly 介绍
App-vNext/Polly
0 意見:
張貼留言