在 .NET Core 中結合 HttpClientFactory 使用 Polly(中篇)
譯者:王亮
作者:Polly 團隊
原文: ofollow,noindex" target="_blank">http://t.cn/EhZ90oq
宣告:我翻譯技術文章不是逐句翻譯的,而是根據我自己的理解來表述的(包括標題)。其中可能會去除一些不影響理解但本人實在不知道如何組織的句子
譯者序:這是“ Polly and HttpClientFactory ”這篇Wiki文件翻譯的中篇,你可以 點選這裡檢視上篇 。接下來的兩篇則是在這個基礎上進行加強。本篇(中篇)主要講如何在ASP.NET Core中通過HttpClientFactory配置Polly策略。如果你對ASP.NET Core 2.1新引入的HttpClient工廠還比較陌生,建議先閱讀我的另一篇文章 .NET Core中正確使用 HttpClient的姿勢 ,這有助於更好地理解本文。
下面主要講如何在ASP.NET Core中通過HttpClientFactory配置Polly策略。
使用 AddTransientHttpErrorPolicy
讓我們先回到上篇的例子:
services.AddHttpClient("GitHub", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); }) .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }));
這裡用了一個新的AddTransientHttpErrorPolicy方法,它可以很方便地配置一個策略來處理下面這些典型的HTTP呼叫錯誤:
- 網路錯誤(HttpRequestException 異常)
- HTTP狀態碼 5XX(伺服器錯誤)
- HTTP狀態碼 408(請求超時)
AddTransientHttpErrorPolicy方法添加了一個策略,這個策略預設預配置了上面HTTP錯誤的過濾器。在builder => builder子句中,你可以定義策略如何處理這些錯誤,還可以配置Polly提供的其它策略,比如重試(如上例所示)、斷路或回退等。
在AddTransientHttpErrorPolicy中處理網路錯誤、HTTP 5XX和HTTP 408是一種便捷的方式,但這不是必需的。如果此方法內建的錯誤過濾器不適合您的需要(你需要仔細考慮一下),您可以擴充套件它,或者構建一個完全定製的Polly策略。
擴充套件 AddTransientHttpErrorPolicy
AddTransientHttpErrorPolicy方法也可以從Polly的一個擴充套件包Polly.Extensions.Http中得到,它在上面的基礎上進行了擴充套件。例如下面配置的策略可以處理429狀態碼:
using Polly.Extensions.Http; // ... var policy = HttpPolicyExtensions .HandleTransientHttpError() // HttpRequestException, 5XX and 408 .OrResult(response => (int)response.StatusCode == 429) // RetryAfter .WaitAndRetryAsync(/* etc */);
使用典型Polly語法配置好的策略
Polly 還有另一個擴充套件方法是AddPolicyHandler,它的一個過載方法可以接收任意IAsyncPolicy引數,所以你可以用典型的Polly語法先定義好任意的一個策略(返回型別為IAsyncPolicy),然後再傳給AddPolicyHandler擴充套件方法。
下面這個例子演示了用AddPolicyHandler來新增一個策略,其中我們編寫了自己的錯誤處理策略:
var retryPolicy = Policy.Handle<HttpRequestException>() .OrResult<HttpResponseMessage>(response => MyCustomResponsePredicate(response)) .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) })); services.AddHttpClient("GitHub", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); }) .AddPolicyHandler(retryPolicy);
類似的,你還可以配置其它策略,比如超時策略:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10); services.AddHttpClient(/* etc */) .AddPolicyHandler(timeoutPolicy);
所有通過HttpClient的呼叫返回的都是一個HttpResponseMessage物件,因此配置的策略必須是IAsyncPolicy物件(譯註:HTTP請求返回的是HttpResponseMessage物件,Polly定義的策略是一個IAsyncPolicy物件,所以AddPolicyHandler方法接收的引數是這兩者的結合體IAsyncPolicy物件)。非泛型的IAsyncPolicy可以通過下面的方式轉換成泛型的IAsyncPolicy:
var timeoutPolicy = Policy.TimeoutAsync(10); services.AddHttpClient(/* etc */) .AddPolicyHandler(timeoutPolicy.AsAsyncPolicy<HttpResponseMessage>());
應用多個策略
所有策略配置的方法也可以鏈式地配置多個策略,例如:
services.AddHttpClient(/* etc */) .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) })) .AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync( handledEventsAllowedBeforeBreaking: 3, durationOfBreak: TimeSpan.FromSeconds(30) ));
多個策略被應用的順序
當您配置多個策略時(如上例所示),策略應用於從外部(第一次配置)到內部(最後配置)的順序依次呼叫。在上面的示例中,呼叫的順序是這樣的:
- 首先通過(外部)重試策略,該策略將依次:
- 通過(內部)斷路策略的呼叫,該策略將依次:
- 進行底層HTTP呼叫。
這個示例之所以用此順序的策略是因為當重試策略在兩次嘗試之間等待時,斷路器可能在其中一個時間段(1、5或10秒)內改變狀態(譯註:上面示例中斷路策略是出現3次異常就“休息”30分鐘)。斷路策略被配置在重試策略的內部,因此每執行一次重試就會執行其內部的斷路策略。
上面的例子應用了兩個策略(重試和斷路),任意數量的策略都是可以的。一個常見的多個策略組合可能是這樣的:重試、斷路和超時(“下篇”會有例子)。
對於那些熟悉Polly的策略包的人來說,使用上面的方式配置多個策略完全等同於使用策略包,也適用於所有“策略包的使用建議”(連結:http://t.cn/EhJ4jfN)。
動態選擇策略
AddPolicyHandler的過載方法允許你根據HTTP請求動態選擇策略。
其中一個用例是對非等冪的操作應用不同的策略行為(譯註:“等冪“指的是一個操作重複使用,始終都會得到同一個結果)。對於HTTP請求來說,POST操作通常不是冪等的(譯註:比如註冊),PUT操作應該是冪等的。所以對於給定的API可能不是一概而論的。比如,您可能想要定義一個策略,讓它只重試GET請求,但不重試其他HTTP謂詞,比如這個示例:
var retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }); var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>(); services.AddHttpClient(/* etc */) // 如果是GET請求,則使用重試策略,否則使用空策略 .AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOpPolicy);
上面的空策略會被應用於所有非GET的請求。空策略只是一種佔坑模式,實際上不做任何事情。
從策略的註冊池中選擇策略
Polly還提供了策略註冊池(請參閱: http://t.cn/Ehi1SQp ),它相當於策略的儲存中心,被註冊的策略可以讓你在應用程式的多個位置重用。AddPolicyHandler的一個過載方法允許您從註冊池中選擇策略。
下面的示例使用IServiceCollection新增一個策略註冊池服務,向註冊池中新增一些策略,然後使用註冊池中的不同策略定義兩個呼叫邏輯。
var registry = services.AddPolicyRegistry(); registry.Add("defaultretrystrategy", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(/* etc */)); registry.Add("defaultcircuitbreaker", HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(/* etc */)); services.AddHttpClient(/* etc */) .AddPolicyHandlerFromRegistry("defaultretrystrategy"); services.AddHttpClient(/* etc */) .AddPolicyHandlerFromRegistry("defaultretrystrategy") .AddPolicyHandlerFromRegistry("defaultcircuitbreaker");
這個示例演示了從註冊池中選擇一個或多個策略應用在不同的HttpClient上,同一個策略被重複使用了兩次。策略註冊池的更復雜用例包括從外部動態更新註冊池中的策略,以便在執行期間動態重新配置策略(請查閱 http://t.cn/Ehidgqy 瞭解更多)。