ASP.Net Core2.1中的HttpClientFactory系列二:整合Polly處理瞬態故障
前言:最近,同事在工作中遇到了使用HttpClient,有些請求超時的問題,輔導員讓我下去調研一下,HttpClinet的使用方式已經改成了之前部落格中提到的方式,問題的原因我已經找到了,就是因為使用了偽非同步,導致阻塞主執行緒。在之前的部落格中有園友,建議在使用靜態的HttpClinet時務必使用它的Async方法,所以就得從頭到尾非同步化。這一點在之前的文章中沒有提,這裡作為補充,也感謝這位園友。關於怎麼使用非同步程式設計,在這裡我就不聊了,大家可以看看其他的部落格,看完公司的程式碼之後,我想強調的是,在使用非同步程式設計的時候,關於返回值的問題:
為什麼async方法返回的通常都是Task或者Task<T>,而不是T本身?這是因為,Task和Task<T>代表著在將來某一個時刻將會返回T型別的結果。因此,在主執行緒呼叫HttpPostWhitStrBody時,實際上你拿到的是一個未來才會發生的預期,也就是未來的某一個時間會得到一個string的結果。如果返回的是一個T本身,那麼,在主執行緒呼叫時就會因為訪問這個需要一段時間才能給出結果,從而阻塞了主執行緒。因此,如果async方法有返回值,應返回Task<T>。如果沒有返回值,應該返回Task。大家如果不太明白的話,建議多瞭解一下C#中的非同步程式設計。好了,前戲太多了,下面就來聊聊如何整合Polly。
一、在非同步程式設計中如何處理異常資訊
在聊如何整合Polly前,我們先來看看在非同步程式設計中如何處理異常。當非同步操作發生異常的時候,異常會停留在非同步方法中,呼叫方法無法直接看到,因此,我們應該非同步方法中處理異常,而不是在呼叫方法中處理異常。如果我們使用了await修飾了任務,那麼,只需要為它包上一層try-catch就可以了。當然了,也可以在呼叫方法(比如Main方法中)捕捉異常,這就需要異常從非同步方法中傳播給呼叫方法。做到這件事是很容易的,只需要兩個條件:
(1)呼叫方法本身也是async的,並且,在內部呼叫非同步方法,並使用await。
(2)非同步方法返回Task或者Task<T>
因為C#不允許在Main方法中使用async(在C#7.1中,可以使用async修飾Main方法了),因此,我們不得不再建立一層方法,下面通過程式碼演示一下。
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp3 { class Program { static void Main(string[] args) { Caller(); Console.ReadKey(); }View Codestatic async void Caller() { try { await UseAsync(0); } catch (AggregateException e) { Console.WriteLine(e.Message); } } static async Task<bool> UseAsync(int number) { Console.WriteLine("非同步方法執行:"+ Thread.CurrentThread.ManagedThreadId); var ret = await Task.Run(() => IsPrimeLowAsync(number)); return ret; } static bool IsPrimeLowAsync(int number) { if (number <= 0) throw new AggregateException("輸入必須大於0"); if (number == 1) return false; for (int i = 0; i < number; i++) { if (number % i == 0) return false; } return true; } } }
一般在處理異常的時候,我們都是採用 try-catch來做處理的,若我們想重試三次,此時我們只能進行迴圈三次操作。我們只能簡單進行處理,自從有了Polly,什麼重試機制,超時都不在話下,下面把話題轉向Polly。
在聊下面的話題時,建議大家先認真閱讀一下這篇部落格,因為博主講的非常細緻:Polly
二、整合Polly,處理HTTP請求過程中的瞬時故障
Polly是一種流行的瞬態故障處理庫,它提供了一種機制來定義可在某些故障發生時應用的策略。 最常用的策略之一就是重試策略。 這中策略允許您包裝一些程式碼,如果發生故障,將重試這些程式碼; 必要時也可以重試多次。 這在您的應用程式需要與外部服務通訊的情況下非常有用。 當通過HTTP與服務進行通訊時,會出現瞬態故障,這種風險始終存在。 瞬態故障可能會阻礙您的請求完成,但是瞬態故障也可能是暫時性的問題。因此, 這使得在這些情況下重試成為明智的選擇。
除了重試之外,Polly還提供了許多其他型別的策略,其中許多策略可能需要與重試相結合,以構建處理故障的複雜方法。 我將在本文中介紹一些更一般的例子,但是如果你想要更全面的瞭解,我建議你檢視一下Polly wiki。
- 使用Polly
ASP.NET團隊與Polly的主要維護者Dylan和Joel密切合作,使得將Polly策略應用於HttpClient例項非常簡單。在開始之前我們先引用下面的兩個包:
這個Microsoft.Extensions.Http.Polly包在IHttpClientBuilder上包含一個名為AddPolicyHandler的擴充套件方法,我們可以使用它來新增一個handler ,該handler 將使用一個Polly例項,來包裝請求。
我們可以用這個擴充套件在我們的ConfigureServices 方法中,程式碼如下:
services.AddHttpClient("github") .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)));
在這個例子中,我們定義了一個名字為“github”的客戶端,並且我們使用AddPolicyHandler 方法來添加了一種處理超時的策略,這裡提供的超時策略,必須是IAsyncPolicy<HttpResponseMessage>,這個中策略在任何請求超過10s都會觸發。
- 重試策略
如果可能的話,當我們在使用Polly時,最好的嘗試是,定義一次策略並在應用相同策略的情況下共享它們,這樣要更改策略,只需在一個位置進行更改。此外,它還確保僅分配策略一次。當然了,如果多個使用者希望通過相同的斷路器例項執行,則需要共享諸如斷路器之類的策略。不太理解,不要緊,下面看程式碼,體會一下。
var retryPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)); services.AddHttpClient("github") .AddPolicyHandler(retryPolicy); services.AddHttpClient("google") .AddPolicyHandler(retryPolicy);
- 瞬時錯誤處理
處理HTTP請求時,我們要處理的最常的問題就是瞬態故障。 由於這是一個常見的要求,Microsoft.Extensions.Http.Polly軟體包中包含一個特定的擴充套件,我們可以使用它來快速設定處理瞬時故障的策略。
例如,要在指定客戶端的請求發生瞬時故障時新增基本重試,我們可以按如下方式註冊重試策略:
services.AddHttpClient("github") .AddTransientHttpErrorPolicy(p => p.RetryAsync(3));
程式碼的含義是,所以使用命名的HttpClient,發出的請求,只要遇到錯誤,就會重試三次。這個AddTransientHttpErrorPolicy 方法需要一個Func<PolicyBuilder<HttpResponseMessage>, IAsyncPolicy<HttpResponseMessage>>.型別的引數。此處的PolicyBuilder將預先配置為處理HttpRequestExceptions,任何返回5xx狀態程式碼的響應以及具有408(請求超時)狀態程式碼的任何響應。 這應該適用於許多情況。 如果您要求在其他條件下應用策略,則需要使用不同的過載來傳遞更具體的策略。
我們需要意識到, 在進行重試時,我們需要考慮冪等性。 重試HTTP GET是一種非常安全的操作。因為HTTP GET本身就是冪等性的, 如果我們呼叫一個方法但沒有收到任何響應,我們可以安全地重試呼叫而不會有任何危險。 但是,請考慮如果我們重試HTTP POST請求會發生什麼? 在這種情況下,我們必須更加小心,因為您的原始請求可能實際收到,但我們收到的響應卻顯示失敗。 在這種情況下,重試可能導致資料重複或下游系統中儲存的資料損壞。 在這裡,您需要更多地瞭解下游服務在多次收到相同請求時將執行的操作。 重試是一種安全操作? 當您擁有下游服務時,更容易控制它。 例如,您可以使用一些唯一識別符號來防止重複的POST。
如果您對下游系統的控制較少,或者您知道重複的POST可能會產生負面影響,則需要更仔細地控制策略。 可能適合的做法是定義不同的命名/型別客戶端。 您可以為那些沒有副作用的請求建立一個,而為那些有副作用的請求建立另一個。 然後,您可以使用正確的客戶端進行操作。 但是,這可能會變得有點難以管理。 更好的選擇是使用AddPolicyHandler的過載,它允許我們訪問HttpRequestMessage,以便可以有條件地應用策略。 那個過載看起來像這樣:AddPolicyHandler(Func<HttpRequestMessage, IAsyncPolicy<HttpResponseMessage>> policySelector),您將注意到此處的policySelector委託可以訪問HttpRequestMessage,並且應該返回IAsyncPolicy <HttpResponseMessage>。 我們無法訪問PolicyBuilder設定來處理瞬態錯誤,就像我們在前面的示例中所做的那樣。 如果我們想要處理常見的瞬態錯誤,我們需要為我們的策略定義預期條件。 為了簡化這一過程,Polly專案包含一個幫助擴充套件,我們可以使用它來設定一個準備好處理常見瞬態錯誤的PolicyBuilder。 要使用擴充套件方法,我們需要從Nuget新增Polly.Extensions.Http包。
然後,我們可以呼叫HttpPolicyExtensions.HandleTranisentHttpError()來獲取配置瞬態故障條件的PolicyBuilder。 我們可以使用該PolicyBuilder建立一個合適的重試策略,當請求是HTTP GET時,可以有條件地應用該策略。 在此示例中,任何其他HTTP方法都使用NoOp策略。
var retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() .RetryAsync(3); var noOp = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>(); services.AddHttpClient("github") .AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOp);
- 使用PolicyRegistry
我想在本文中介紹的最後一個示例是如何從策略登錄檔中應用策略。 為了支援策略重用,Polly提供了PolicyRegistry的概念,PolicyRegistry本質上是策略的容器。 這些可以在應用程式啟動時通過向登錄檔新增策略來定義。 然後可以傳遞登錄檔並用於按名稱訪問策略。IHttpClientBuilder上可用的擴充套件還支援使用登錄檔將基於Polly的處理程式新增到客戶端。
var registry = services.AddPolicyRegistry(); var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)); var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(30)); registry.Add("regular", timeout); registry.Add("long", longTimeout); services.AddHttpClient("github") .AddPolicyHandlerFromRegistry("regular");
首先,我們必須在DI中註冊PolicyRegistry。 Microsoft.Extensions.Http.Polly包中包含一些擴充套件方法,以簡化此操作。 在上面的示例中,我呼叫AddPolicyRegistry方法,該方法是IServiceCollection的擴充套件。 這將建立一個新的PolicyRegistry,並在DI中添加註冊,作為IPolicyRegistry <string>和IReadOnlyPolicyRegistry <string>的實現。 該方法返回策略,以便我們有權向其新增策略。
在此示例中,我們添加了兩個超時策略併為其指定了名稱。 現在,在註冊客戶端時,我們可以呼叫IHttpClientBuilder上的AddPolicyHandlerFromRegistry方法。 這將採用我們想要使用的策略的名稱。 當工廠建立此命名客戶端的例項時,它將新增適當的處理程式,在“regular”重試策略中包含呼叫,該策略將從登錄檔中檢索。
示例專案:新建一個.Net Core 2.1的webapi專案:
Startup.cs檔案的程式碼:
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("GitHub", client => { client.BaseAddress = new Uri("https://api.github.co"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); }) .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) })); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
注意:
WaitAndRetryAsync引數的意思是:每次重試時等待的睡眠持續時間。
ValuesController的程式碼:
private readonly IHttpClientFactory _httpClientFactory; public ValuesController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } // GET api/values [HttpGet] public async Task<ActionResult> Get() { var client = _httpClientFactory.CreateClient("GitHub"); string result = await client.GetStringAsync("/"); return Ok(result); }
看到沒,它在重試。
更多的Polly和HttpClinetFactory的整合使用請參考:
https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory
https://www.hanselman.com/blog/AddingResilienceAndTransientFaultHandlingToYourNETCoreHttpClientWithPolly.aspx
三、總結
注意:AddTransientHttpErrorPolicy方法會自動幫我們處理以下錯誤:
(1)Network failures (System.Net.Http.HttpRequestException)
(2)HTTP 5XX status codes (server errors)
(3)HTTP 408 status code (request timeout)
通過這些庫,您可以輕鬆地啟動並執行能夠無縫處理瞬態故障的HttpClient例項。 有關更詳細的Polly文件和示例,建議您檢視Polly wiki。這裡只是聊了關於HttpClientFactory中整合Polly的基礎用法,關於更詳細的使用請參考:https://www.cnblogs.com/CreateMyself/p/7589397.html,好了今天就聊到這裡,該系列文章還有最後一篇,對於Polly我也是剛接觸,至於專案中是否使用還要經過輔導員的稽核,希望對你有幫助,謝謝。
參考文章:
(翻譯)https://www.stevejgordon.co.uk/httpclientfactory-using-polly-for-transient-fault-handling
作者:郭崢
出處:http://www.cnblogs.com/runningsmallguo/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。