ASP.Net Core2.1中的HttpClientFactory系列二:集成Polly處理瞬態故障

 

  前言:最近,同事在工做中遇到了使用HttpClient,有些請求超時的問題,輔導員讓我下去調研一下,HttpClinet的使用方式已經改爲了以前博客中提到的方式,問題的緣由我已經找到了,就是由於使用了僞異步,致使阻塞主線程。在以前的博客中有園友,建議在使用靜態的HttpClinet時務必使用它的Async方法,因此就得從頭至尾異步化。這一點在以前的文章中沒有提,這裏做爲補充,也感謝這位園友。關於怎麼使用異步編程,在這裏我就不聊了,你們能夠看看其餘的博客,看完公司的代碼以後,我想強調的是,在使用異步編程的時候,關於返回值的問題:html

爲何async方法返回的一般都是Task或者Task<T>,而不是T自己?這是由於,Task和Task<T>表明着在未來某一個時刻將會返回T類型的結果。所以,在主線程調用HttpPostWhitStrBody時,實際上你拿到的是一個將來纔會發生的預期,也就是將來的某一個時間會獲得一個string的結果。若是返回的是一個T自己,那麼,在主線程調用時就會由於訪問這個須要一段時間才能給出結果,從而阻塞了主線程。所以,若是async方法有返回值,應返回Task<T>。若是沒有返回值,應該返回Task。你們若是不太明白的話,建議多瞭解一下C#中的異步編程。好了,前戲太多了,下面就來聊聊如何集成Polly。git

 

1、在異步編程中如何處理異常信息

在聊如何集成Polly前,咱們先來看看在異步編程中如何處理異常。當異步操做發生異常的時候,異常會停留在異步方法中,調用方法沒法直接看到,所以,咱們應該異步方法中處理異常,而不是在調用方法中處理異常。若是咱們使用了await修飾了任務,那麼,只須要爲它包上一層try-catch就能夠了。固然了,也能夠在調用方法(好比Main方法中)捕捉異常,這就須要異常從異步方法中傳播給調用方法。作到這件事是很容易的,只須要兩個條件:github

(1)調用方法自己也是async的,而且,在內部調用異步方法,並使用await。web

(2)異步方法返回Task或者Task<T>編程

由於C#不容許在Main方法中使用async(在C#7.1中,可使用async修飾Main方法了),所以,咱們不得再也不建立一層方法,下面經過代碼演示一下。json

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {

            Caller();


            Console.ReadKey();
        }

        static 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;
        }
    }
}
View Code

 

通常在處理異常的時候,咱們都是採用 try-catch來作處理的,若咱們想重試三次,此時咱們只能進行循環三次操做。咱們只能簡單進行處理,自從有了Polly,什麼重試機制,超時都不在話下,下面把話題轉向Polly。api

在聊下面的話題時,建議你們先認真閱讀一下這篇博客,由於博主講的很是細緻:Polly安全

 

2、集成Polly,處理HTTP請求過程當中的瞬時故障

   Polly是一種流行的瞬態故障處理庫,它提供了一種機制來定義可在某些故障發生時應用的策略。 最經常使用的策略之一就是重試策略。 這中策略容許您包裝一些代碼,若是發生故障,將重試這些代碼; 必要時也能夠重試屢次。 這在您的應用程序須要與外部服務通訊的狀況下很是有用。 當經過HTTP與服務進行通訊時,會出現瞬態故障,這種風險始終存在。 瞬態故障可能會阻礙您的請求完成,可是瞬態故障也多是暫時性的問題。所以, 這使得在這些狀況下重試成爲明智的選擇。app

   除了重試以外,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

 

3、總結

  注意: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/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

相關文章
相關標籤/搜索