已被.NET基金會承認的彈性和瞬態故障處理庫Polly介紹

前言

本節咱們來介紹一款強大的庫Polly,Polly是一種.NET彈性和瞬態故障處理庫,容許咱們以很是順暢和線程安全的方式來執諸如行重試,斷路,超時,故障恢復等策略。 Polly針對對.NET 4.0,.NET 4.5和.NET Standard 1.1以及.NET Core實現,該項目做者現已成爲.NET基金會一員,項目一直在不停迭代和更新,項目地址【https://github.com/App-vNext/Polly】,你值得擁有。接下來咱們以.NET Framework  4.5來演示它的強大功能。git

Introduce Polly

首先咱們得下載Polly包,最新版本爲5.3.1,以下:github

該庫實現了七種恢復策略,下面我一一爲您來介紹。數據庫

重試策略(Retry)

重試策略針對的前置條件是短暫的故障延遲且在短暫的延遲以後可以自我糾正。容許咱們作的是可以自動配置重試機制。緩存

斷路器(Circuit-breaker)

斷路器策略針對的前置條件是當系統繁忙時,快速響應失敗總比讓用戶一直等待更好。保護系統故障免受過載,Polly能夠幫其恢復。安全

超時(Timeout)

超時策略針對的前置條件是超過必定的等待時間,想要獲得成功的結果是不可能的,保證調用者沒必要等待超時。微信

隔板隔離(Bulkhead Isolation)

隔板隔離針對的前置條件是當進程出現故障時,多個失敗一直在主機中對資源(例如線程/ CPU)一直佔用。下游系統故障也可能致使上游失敗。這兩個風險都將形成嚴重的後果。都說一粒老鼠子屎攪渾一鍋粥,而Polly則將受管制的操做限制在固定的資源池中,免其餘資源受其影響。網絡

緩存(Cache)

緩存策略針對的前置條件是數據不會很頻繁的進行更新,爲了不繫統過載,首次加載數據時將響應數據進行緩存,若是緩存中存在則直接從緩存中讀取。框架

回退(Fallback)

操做仍然會失敗,也就是說當發生這樣的事情時咱們打算作什麼。也就是說定義失敗返回操做。async

策略包裝(PolicyWrap)

策略包裝針對的前置條件是不一樣的故障須要不一樣的策略,也就意味着彈性靈活使用組合。ide

幾種策略使用

一旦從事IT就得警戒異常並友好擁抱異常而非漠不關心,這個時候咱們利用try{}catch{}來處理。

            try
            {
                var a = 0;
                var b = 1 / a;
            }
            catch (DivideByZeroException ex)
            {

                throw ex;
            }

若咱們想重試三次,此時咱們只能進行循環三次操做。咱們只能簡單進行處理,自從有了Polly,什麼重試機制,超時都不在話下,下面咱們來簡短介紹各類策略。Polly默認處理策略須要指定拋出的具體異常或者執行拋出異常返回的結果。處理單個類型異常以下:

Policy
  .Handle<DivideByZeroException>()

上述異常指嘗試除以0,下面咱們演示下具體使用,咱們嘗試除以0並用Polly指定該異常並重試三次。

        static int Compute()
        {
            var a = 0;
            return 1 / a;
        }
            try
            {
                var retryTwoTimesPolicy =
                     Policy
                         .Handle<DivideByZeroException>()
                         .Retry(3, (ex, count) =>
                         {
                             Console.WriteLine("執行失敗! 重試次數 {0}", count);
                             Console.WriteLine("異常來自 {0}", ex.GetType().Name);
                         });
                retryTwoTimesPolicy.Execute(() =>
                {
                    Compute();
                });
            }
            catch (DivideByZeroException e)
            {
                Console.WriteLine($"Excuted Failed,Message: ({e.Message})");

            }

若是咱們想指定處理多個異常類型經過OR便可。

Policy
  .Handle<DivideByZeroException>()
  .Or<ArgumentException>()

固然還有更增強大的功能,好比在微信支付時,微信回調咱們的應用程序時,此時若失敗,想必微信那邊也會作重試機制,例如隔一段時間重試調用一次,重複調用幾回後仍失敗則再也不回調。咱們利用Polly則能夠演示等待重試機制。

        /// <summary>
        /// 拋出異常
        /// </summary>
        static void ZeroExcepcion()
        {
            throw new DivideByZeroException();
        }
        /// <summary>
        /// 異常信息
        /// </summary>
        /// <param name="e"></param>
        /// <param name="tiempo"></param>
        /// <param name="intento"></param>
        /// <param name="contexto"></param>
        static void ReportaError(Exception e, TimeSpan tiempo, int intento, Context contexto)
        {
            Console.WriteLine($"異常: {intento:00} (調用秒數: {tiempo.Seconds} 秒)\t執行時間: {DateTime.Now}");
        }
            try
            {
                var politicaWaitAndRetry = Policy
                    .Handle<DivideByZeroException>()
                    .WaitAndRetry(new[]
                    {
                        TimeSpan.FromSeconds(1),
                        TimeSpan.FromSeconds(3),
                        TimeSpan.FromSeconds(5),
                        TimeSpan.FromSeconds(7)
                    }, ReportaError);
                politicaWaitAndRetry.Execute(() =>
                {
                    ZeroExcepcion();
                });
            }
            catch (Exception e)
            {
                Console.WriteLine($"Executed Failed,Message:({e.Message})");
            }

咱們講完默認策略和重試策略,再來看看反饋策略,翻譯的更通俗一點則是執行失敗後返回的結果,此時要爲Polly指定返回類型,而後指定異常,最後調用Fallback方法。

        static string ThrowException()
        {
            throw new Exception();
        }
           var fallBackPolicy =
                Policy<string>
                    .Handle<Exception>()
                    .Fallback("執行失敗,返回Fallback");

            var fallBack = fallBackPolicy.Execute(() =>
            {
                return ThrowException();
            });
            Console.WriteLine(fallBack);

包裹策略說到底就是混合多種策略,並執行。

          var fallBackPolicy =
                Policy<string>
                    .Handle<Exception>()
                    .Fallback("執行失敗,返回Fallback");

            var fallBack = fallBackPolicy.Execute(() =>
            {
                return ThrowException();
            });
            Console.WriteLine(fallBack);

            var politicaWaitAndRetry = 
                Policy<string>
                    .Handle<Exception>()
                    .Retry(3, (ex, count) =>
                    {
                        Console.WriteLine("執行失敗! 重試次數 {0}", count);
                        Console.WriteLine("異常來自 {0}", ex.GetType().Name);
                    });

            var mixedPolicy = Policy.Wrap(fallBackPolicy, politicaWaitAndRetry);
            var mixedResult = mixedPolicy.Execute(ThrowException);
            Console.WriteLine($"執行結果: {mixedResult}");

至此關於Polly的基本介紹就已結束,該庫仍是很是強大,更多特性請參考上述github例子,接下來咱們來看看兩種具體場景。

ASP.NET Web APi使用Polly重試機制

在Polly v4.30中以上能夠利用HandleResult指定返回結果,以下:

Policy
  .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)

基於此咱們徹底能夠利用執行Web APi中的響應策略,以下:

 public readonly RetryPolicy<HttpResponseMessage> _httpRequestPolicy;

拿到響應中狀態碼,若爲500則重試三次。

 _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
            r => r.StatusCode == HttpStatusCode.InternalServerError)
            .WaitAndRetryAsync(3,
            retryAttempt => TimeSpan.FromSeconds(retryAttempt));

上述獲取請求響應策略在構造函數中獲取。

    public class PollyController : ApiController
    {
        public readonly RetryPolicy<HttpResponseMessage> _httpRequestPolicy;
        public PollyController()
        {
            _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
            r => r.StatusCode == HttpStatusCode.InternalServerError)
            .WaitAndRetryAsync(3,
            retryAttempt => TimeSpan.FromSeconds(retryAttempt));
        }
    }

此時調用接口時執行策略的Execute或者ExecuteAsync方法便可。

        public async Task<IHttpActionResult> Get()
        {
            var httpClient = new HttpClient();
            string requestEndpoint = "http://localhost:4096";

            HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));

            IEnumerable<string> numbers = await httpResponse.Content.ReadAsAsync<IEnumerable<string>>();

            return Ok(numbers);
        }

你覺得僅限於在Web APi中使用嗎?在其餘框架中也可使用,例如EntityFramework 6.x中,在EntityFramework 6+上出現了執行策略,也就是執行重試機制,這個時候咱們依然能夠藉助Polly輪子來實現。

EntityFramework 6.x使用Polly重試機制

在EntityFramework 6.x中有以下執行策略接口,看起來是否是和Polly中的Execute方法是否是很相似。

    //
    // 摘要:
    //     A strategy that is used to execute a command or query against the database, possibly
    //     with logic to retry when a failure occurs.
    public interface IDbExecutionStrategy
    {
        //
        // 摘要:
        //     Indicates whether this System.Data.Entity.Infrastructure.IDbExecutionStrategy
        //     might retry the execution after a failure.
        bool RetriesOnFailure { get; }

        //
        // 摘要:
        //     Executes the specified operation.
        //
        // 參數:
        //   operation:
        //     A delegate representing an executable operation that doesn't return any results.
        void Execute(Action operation);
        //
        // 摘要:
        //     Executes the specified operation and returns the result.
        //
        // 參數:
        //   operation:
        //     A delegate representing an executable operation that returns the result of type
        //     TResult.
        //
        // 類型參數:
        //   TResult:
        //     The return type of operation.
        //
        // 返回結果:
        //     The result from the operation.
        TResult Execute<TResult>(Func<TResult> operation);
        //
        // 摘要:
        //     Executes the specified asynchronous operation.
        //
        // 參數:
        //   operation:
        //     A function that returns a started task.
        //
        //   cancellationToken:
        //     A cancellation token used to cancel the retry operation, but not operations that
        //     are already in flight or that already completed successfully.
        //
        // 返回結果:
        //     A task that will run to completion if the original task completes successfully
        //     (either the first time or after retrying transient failures). If the task fails
        //     with a non-transient error or the retry limit is reached, the returned task will
        //     become faulted and the exception must be observed.
        Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken);
        //
        // 摘要:
        //     Executes the specified asynchronous operation and returns the result.
        //
        // 參數:
        //   operation:
        //     A function that returns a started task of type TResult.
        //
        //   cancellationToken:
        //     A cancellation token used to cancel the retry operation, but not operations that
        //     are already in flight or that already completed successfully.
        //
        // 類型參數:
        //   TResult:
        //     The result type of the System.Threading.Tasks.Task`1 returned by operation.
        //
        // 返回結果:
        //     A task that will run to completion if the original task completes successfully
        //     (either the first time or after retrying transient failures). If the task fails
        //     with a non-transient error or the retry limit is reached, the returned task will
        //     become faulted and the exception must be observed.
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation, CancellationToken cancellationToken);
    }

EntityFramework 6.x中的執行策略說到底就是數據庫鏈接問題即彈性鏈接,若考慮到數據庫過渡負載問題,此時應用程序和數據庫之間存在網絡問題的話。可能數據庫鏈接在幾秒內才返回,此時也沒有什麼很大的問題,咱們徹底能夠再嘗試一次,此時或許過了鏈接頻繁期,保證鏈接立馬恢復。若是數據庫鏈接一會恢復不了呢?或許是五分鐘,又或者是半個小時。若是咱們只是一味盲目的進行重試,這顯然不可取。若是咱們的應用程序鏈接超時時間超過了20秒,若咱們選擇繼續鏈接到數據庫,咱們將很快用完咱們應用程序池中的工做線程。一直等待數據庫的響應。此時網站將徹底無響應,同時會給用戶頁面無響應的友好提醒。這是Polly庫中描述斷路器的很好例子,換句話說若是咱們捕獲了m個數量的SqlExceptions,假設數據庫有其餘問題存在,致使咱們不能在n秒內再嘗試鏈接數據庫。此時在數據庫鏈接上存在一個問題,那就是阻塞了咱們的應用程序工做線程被掛起,咱們試圖鏈接數據庫,咱們假設不可用的話,可是咱們要打破這種不可用,那就用Polly吧。

 

咱們看到上述EntityFramework 6.x實現了IDbExecutionStrategy接口,但沒有實現如Polly中的斷路器模式,EntityFramework 6.x中的執行策略只是重試機制而已。 好比SqlAzureExecutionStrategy將在指定的時間段內重試指定的次數,直到一段時間段過去,重試指數事後,接着就是失敗。 同時全部後續調用將執行相同操做,重試並失敗。 這是調用數據庫時最好的策略嗎? 不敢確定,或許Polly中的斷路器模式值得咱們借鑑。咱們本身來實現上述執行策略接口。

    public class CirtuitBreakerExecutionStrategy : IDbExecutionStrategy
    {
        private Policy _policy;

        public CirtuitBreakerExecutionStrategy(Policy policy)
        {
            _policy = policy;
        }

        public void Execute(Action operation)
        {

            _policy.Execute(() =>
            {
                operation.Invoke();
            });
        }

        public TResult Execute<TResult>(Func<TResult> operation)
        {
            return _policy.Execute(() =>
            {
                return operation.Invoke();
            });
        }

        public async Task ExecuteAsync(Func<Task> operation, CancellationToken cancellationToken)
        {
            await _policy.ExecuteAsync(() =>
            {
                return operation.Invoke();
            });
        }

        public async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation, CancellationToken cancellationToken)
        {
            return await _policy.ExecuteAsync(() =>
            {
                return operation.Invoke();
            });
        }

        public bool RetriesOnFailure { get { return true; } }
    }

接下來在基於代碼配置文件中設置咱們上述自定義實現的斷路器模式。

    public class EFConfiguration : DbConfiguration
    {
        public Policy _policy;
        public EFConfiguration()
        {
            _policy = Policy.Handle<Exception>().CircuitBreaker(3, TimeSpan.FromSeconds(60));

            SetExecutionStrategy("System.Data.SqlClient", () => new CirtuitBreakerExecutionStrategy(_policy));
        }
    }

上述自定義實現執行策略不保證必定有用或許也是一種解決方案呢。

總結

本節咱們介紹了強大的Polly庫和其對應使用的兩種實際場景,有此輪子咱們何不用起,將其進行封裝能夠用於一切重試、緩存、異常等處理。 

個人博客即將入駐「雲棲社區」,誠邀技術同仁一同入駐。
相關文章
相關標籤/搜索