.net core微服務入門之Polly


Polly極簡介紹

Polly是一個被.NET基金會認可的彈性和瞬態故障處理庫,允許我們以非常順暢和線程安全的方式來執諸如行重試,斷路,超時,故障恢復等策略,其主要功能如下:

重試(Retry)
斷路器(Circuit-Breaker)
超時檢測(Timeout)
緩存(Cache)
降級(Fallback)
Polly的策略主要由“故障”和“動作”兩個部分組成,“故障”可以包括異常、超時等情況,“動作”則包括Fallback(降級)、重試(Retry)、熔斷(Circuit-Breaker)等。策略則用來執行業務代碼,當業務代碼出現了“故障”中的情況時就開始執行“動作”。

Polly 錯誤處理使用三步曲

  • 定義條件: 定義你要處理的錯誤異常或返回結果
  • 定義處理方式 : 重試、熔斷、回退
  • 執行

定義條件

  • 定義異常錯誤的條件
            // 單個異常類型
            Policy.Handle<Exception>();

            // 限定條件的單個異常
            Policy.Handle<Exception>(ex => ex.Message == "請求超時");

            // 多個異常類型
            Policy.Handle<Exception>().Or<ArgumentException>();

            // 限定條件的多個異常
            Policy.Handle<Exception>(ex => ex.Message == "請求超時")
                .Or<ArgumentException>(ex => ex.ParamName == "ID");

            // Inner Exception 異常里面的異常類型 
            Policy.HandleInner<Exception>()
                .OrInner<ArgumentException>(ex => ex.ParamName == "ID");
  • 定義返回結果的條件  
            // 返回結果加限定條件 
            Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound);

            // 處理多個返回結果
            Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
                .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway);

            // 處理元類型結果 (用.Equals)
            Policy.HandleResult(HttpStatusCode.InternalServerError)
                .OrResult(HttpStatusCode.BadGateway);

  

定義處理方式

重試

重試很好理解,當發生某種錯誤或者返回某種結果的時候進行重試

  • 按次數重試
            // 重試1次
            Policy.Handle<Exception>().Retry();

            // 重試3次
            Policy.Handle<Exception>().Retry(3);

            // 重試3次,加上重試時的action參數
            Policy.Handle<Exception>().Retry(3, (exception, retryCount) =>
            {
                // do something
            });
  • 不斷重試(直到成功)
            // 不斷重試,直到成功
            Policy.Handle<Exception>().RetryForever();

            // 不斷重試,帶action參數在每次重試的時候執行
            Policy.Handle<Exception>().RetryForever(exception =>
            {
                // do something       
            });
  • 等待之后按次數重試
            // 重試3次,每次等待5s
            Policy.Handle<Exception>().WaitAndRetry(
                3,
                retryAttempt => TimeSpan.FromSeconds(5),
                // 處理異常、等待時間、重試第幾次
                (exception, timespan, retryCount, context) =>
                {
                    // do something
                });

            // 重試3次,分別等待1、2、3秒。
            Policy.Handle<Exception>().WaitAndRetry(new[]
            {
                TimeSpan.FromSeconds(1),
                TimeSpan.FromSeconds(2),
                TimeSpan.FromSeconds(3)
            });

熔斷

熔斷也可以被作為當遇到某種錯誤場景下的一個操作。
在這里插入圖片描述

打開(Open)——熔斷器打開狀態,此時對目標服務的調用都直接返回錯誤,熔斷周期內不會走網絡請求,當熔斷周期結束時進入半開狀態;
關閉(Closed)——關閉狀態下正常發生網絡請求,但會記錄符合熔斷條件的連續執行次數,如果錯誤數量達到設定的閾值(如果在沒有達到閾值之前恢復正常,之前的累積次數將會歸零),熔斷狀態進入到打開狀態;
半開(Half-Open)——半開狀態下允許定量的服務請求,如果調用都成功則認為恢復了,關閉熔斷器,否則認為還沒好,又回到熔斷器打開狀態;

Policy.Handle<Exception>()
                 .CircuitBreaker(
                     // 熔斷前允許出現幾次錯誤
                     3,
                     // 熔斷時間
                     TimeSpan.FromSeconds(5),
                     // 熔斷時觸發
                     onBreak: (ex, breakDelay) =>
                     {
                         Console.WriteLine("斷路器:開啟狀態(熔斷時觸發)");
                     },
                     // 熔斷恢復時觸發
                     onReset: () =>
                     {
                         Console.WriteLine("斷路器:關閉狀態(熔斷恢復時觸發)");
                     },
                     // 熔斷時間到了之后觸發,嘗試放行少量(1次)的請求,
                     onHalfOpen: () =>
                     {
                         Console.WriteLine("斷路器:半開啟狀態(熔斷時間到了之后觸發)");
                     }
                 );

超時檢測(Timeout)

Polly中關於超時的兩個策略:一個是悲觀策略(Pessimistic),一個是樂觀策略(Optimistic)。其中,悲觀策略超時后會直接拋異常,而樂觀策略則不會,而只是觸發CancellationTokenSource.Cancel函數,需要等待委托自行終止操作。一般情況下,我們都會用悲觀策略。

            // 設置超時時間為30s
            Policy.Timeout(30, onTimeout: (context, timespan, task, ex) =>
            {
                // do something 
            });
            
            // 超時分為樂觀超時與悲觀超時,樂觀超時依賴於CancellationToken ,它假設我們的具體執行的任務都支持CancellationToken。
            // 那么在進行timeout的時候,它會通知執行線程取消並終止執行線程,避免額外的開銷。下面的樂觀超時的具體用法 。
            HttpResponseMessage httpResponse = await Policy.TimeoutAsync(30)
            .ExecuteAsync(
                async ct => await new HttpClient().GetAsync(""),
                CancellationToken.None
            );

            // 悲觀超時與樂觀超時的區別在於,如果執行的代碼不支持取消CancellationToken,
            // 它還會繼續執行,這會是一個比較大的開銷。
            Policy.Timeout(30, TimeoutStrategy.Pessimistic);

艙壁

它用來限制某一個操作的最大並發執行數量

            Policy.Bulkhead(12);
            // 同時,我們還可以控制一個等待處理的隊列長度
            Policy.Bulkhead(12, 2);

            // 以及當請求執行操作被拒絕的時候,執行回調
            Policy.Bulkhead(12, context =>
            {
                // do something 
            });

回退(Fallback)

降級的目的就是當某個服務提供者發生故障的時候,向調用方返回一個替代響應或者錯誤響應。

            Policy.Handle<Exception>().Fallback(() =>
            {
                // do something
            });

組合Polly

使用Wrap方法,將多個policy組合起來,其中策略的優先級是右到左。

 var policyWrap = Policy.Wrap(fallback, breaker, timeout, retry, bulkhead);
            policyWrap.Execute(() =>
            {
                // do something
            });

Polly測試項目

  • 添加一個控制台項目,Nuget添加Polly引用
  • 添加創建組合polly代碼
public static ISyncPolicy CreatePolly()
        {
            // 超時1秒
            var timeoutPolicy = Policy.Timeout(1, TimeoutStrategy.Pessimistic, (context, timespan, task) =>
            {
                Console.WriteLine("執行超時,拋出TimeoutRejectedException異常");
            });


            // 重試2次
            var retryPolicy = Policy.Handle<Exception>()
                .WaitAndRetry(
                    2,
                    retryAttempt => TimeSpan.FromSeconds(2),
                    (exception, timespan, retryCount, context) =>
                    {
                        Console.WriteLine($"{DateTime.Now} - 重試 {retryCount} 次 - 拋出{exception.GetType()}-{timespan.TotalMilliseconds}");
                    });

            // 連續發生兩次故障,就熔斷3秒
            var circuitBreakerPolicy = Policy.Handle<Exception>()
                .CircuitBreaker(
                    // 熔斷前允許出現幾次錯誤
                    2,
                    // 熔斷時間
                    TimeSpan.FromSeconds(5),
                    // 熔斷時觸發 OPEN
                    onBreak: (ex, breakDelay) =>
                    {
                        Console.WriteLine($"{DateTime.Now} - 斷路器:開啟狀態(熔斷時觸發)");
                    },
                    // 熔斷恢復時觸發 // CLOSE
                    onReset: () =>
                    {
                        Console.WriteLine($"{DateTime.Now} - 斷路器:關閉狀態(熔斷恢復時觸發)");
                    },
                    // 熔斷時間到了之后觸發,嘗試放行少量(1次)的請求,
                    onHalfOpen: () =>
                    {
                        Console.WriteLine($"{DateTime.Now} - 斷路器:半開啟狀態(熔斷時間到了之后觸發)");
                    }
                );

            // 回退策略,降級!
            var fallbackPolicy = Policy.Handle<Exception>()
                .Fallback(() =>
                {
                    Console.WriteLine("這是一個Fallback");
                }, exception =>
                {
                    Console.WriteLine($"Fallback異常:{exception.GetType()}");
                });

            // 策略從右到左依次進行調用
            // 首先判斷調用是否超時,如果超時就會觸發異常,發生超時故障,然后就觸發重試策略;
            // 如果重試兩次中只要成功一次,就直接返回調用結果
            // 如果重試兩次都失敗,第三次再次失敗,就會發生故障
            // 重試之后是斷路器策略,所以這個故障會被斷路器接收,當斷路器收到兩次故障,就會觸發熔斷,也就是說斷路器開啟
            // 斷路器開啟的3秒內,任何故障或者操作,都會通過斷路器到達回退策略,觸發降級操作
            // 3秒后,斷路器進入到半開啟狀態,操作可以正常執行
            return Policy.Wrap(fallbackPolicy, circuitBreakerPolicy, retryPolicy, timeoutPolicy);
        }
  • 添加測試代碼
            var policy = CreatePolly();

            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine($"-------------第{i}次請求-------------");
                policy.Execute(() =>
                {
                    // 從10次開始,正常請求成功
                    if (i < 10)
                    {
                        Thread.Sleep(3000);
                    }
                    else
                    {
                        Console.WriteLine($"{DateTime.Now}:請求成功");
                    }
                });
                Thread.Sleep(1000);
            }

測試效果如下圖:
在這里插入圖片描述在這里插入圖片描述

示例代碼

版權聲明:本文為CSDN博主「你的眼睛能看多遠了」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u011210017/article/details/104213454


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM