【微服務學習】Polly:熔斷降級組件

何爲熔斷降級

  「熔斷器如同電力過載保護器。它能夠實現快速失敗,若是它在一段時間內偵測到許多相似的錯誤,會強迫其之後的多個調用快速失敗,再也不訪問遠程服務器,從而防止應用程序不斷地嘗試執行可能會失敗的操做,使得應用程序繼續執行而不用等待修正錯誤,或者浪費時間去等到長時間的超時產生。」   降級的目的是當某個服務提供者發生故障的時候,向調用方返回一個替代響應。   簡單一句話歸納,降級就是在調用的下游服務A出現問題(常見超時),提供PLAN-B,返回的效果可能沒有服務A好,可是聊勝於無。而熔斷器的存在就是要保障什麼時候走到降級方法,什麼時候恢復,以什麼樣的策略恢復。html

.NET Core 熔斷降級實踐

簡介

  Polly是一種.NET彈性和瞬態故障處理庫,容許咱們以很是順暢和線程安全的方式來執諸如行重試,斷路,超時,故障恢復等策略。   Polly當前版本能夠工做在 .NET Standard 1.1 (包括: .NET Framework 4.5-4.6.1, .NET Core 1.0, Mono, Xamarin, UWP, WP8.1+) 和 .NET Standard 2.0+ (包括: .NET Framework 4.6.1, .NET Core 2.0+, 新版本的 Mono, Xamarin and UWP targets).上,同時也爲舊版本的.NET Framework提供了一些可用的舊版本,具體版本對應以下:git

目標框架 最低適配版本 最高適配版本
.NET Standard 2.1
for use with IHttpClientFactory
6.0.1 Current
.NET Standard 2.0 (dedicated target) 6.0.1 Current
.NET Standard 2.0 5.0.3 via .Net Standard 1.0 (upward compatible) Current
.NET Standard 1.1 5.0.3 via .Net Standard 1.0 (upward compatible) Current
.NET Standard 1.0 5.0.3 5.1.0
.NET Framework 4.5 1.0.0 Current (via .Net Standard);
5.9.0 (as dedicated target)
.NET Framework 4.0
with Async support
4.2.2 5.9.0
.NET Framework 4.0 1.0.0 5.9.0
.NET Framework 3.5 1.0.0 4.3.0
Various PCL targets 2.0.0 Current (via .Net Standard);
4.3.0 (as dedicated PCL target)

  該項目做者現已成爲.NET基金會一員,項目一直在不停迭代和更新,項目地址【https://github.com/App-vNext/Polly】github

七種恢復策略

策略 前置條件 此策略解決什麼問題?
重試策略(Retry)
(policy family)
(快速開始 ; 深刻學習)
重試策略針對的前置條件是短暫的故障延遲且在短暫的延遲以後可以自我糾正。 "也許這只是曇花一現" 容許咱們作的是可以自動配置重試機制。
斷路器(Circuit-breaker)
(policy family)
(快速開始 ; 深刻學習)
斷路器策略針對的前置條件是當系統繁忙時,快速響應失敗總比讓用戶一直等待更好。

保護系統故障免受過載,Polly能夠幫其恢復。
"痛了,天然就會放下"

"讓它歇一下"
當故障超過某個預先配置的閾值時, 中斷電路 (塊執行) 一段時間。
超時(Timeout)
(快速開始 ; 深刻學習)
超時策略針對的前置條件是超過必定的等待時間,想要獲得成功的結果是不可能的。 "你沒必要等待,她不會再來" 保證調用者沒必要等待太長時間。
隔板隔離(Bulkhead Isolation)
(快速開始 ; 深刻學習)
隔板隔離針對的前置條件是當進程出現故障時,多個失敗一直在主機中對資源(例如線程/ CPU)一直佔用。下游系統故障也可能致使上游失敗。

這兩個風險都將形成嚴重的後果。
"一顆老鼠屎壞了一鍋湯" 將受管制的操做限制在固定的資源池中,避免其餘資源受其影響。
緩存(Cache)
(快速開始 ; 深刻學習)
數據不會很頻繁的進行更新,相同請求的響應是類似的。 "據說
你還會再來
我翹首以盼"
首次加載數據時將響應數據進行緩存,請求時若緩存中存在則直接從緩存中讀取。
回退(Fallback)
(快速開始 ; 深刻學習)
操做將仍然失敗 - 可是你能夠實現準備好失敗後要作的補救措施。 "你若安好,我備胎到老。" 定義失敗時要返回 (或要執行的操做) 的替代值。.
策略包裝(PolicyWrap)
(快速開始 ; 深刻學習)
不一樣的故障須要不一樣的策略,也就意味着彈性靈活使用組合。 "謀定然後動" 容許靈活地組合上述任何策略。

實踐

故障處理(被動策略)

故障處理策略處理經過策略執行的代碼所引起的特定的異常或返回結果。web

第一步:指定但願處理的異常(可選-指定要處理的返回結果)

指定但願處理的異常:
// 單一異常種類
Policy
  .Handle<HttpRequestException>()

// 帶條件判斷的單一異常
Policy
  .Handle<SqlException>(ex => ex.Number == 1205)

// 多種異常
Policy
  .Handle<HttpRequestException>()
  .Or<OperationCanceledException>()

// 帶條件判斷的多種異常
Policy
  .Handle<SqlException>(ex => ex.Number == 1205)
  .Or<ArgumentException>(ex => ex.ParamName == "example")

// 普通異常或聚合異常的內部異常, 能夠帶有條件
Policy
  .HandleInner<HttpRequestException>()
  .OrInner<OperationCanceledException>(ex => ex.CancellationToken != myToken)
指定要處理的返回結果

從Polly v4.3.0起,包含返回TResult的調用的策略也能夠處理TResult返回值api

// 帶條件判斷的單種返回值處理
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>(HttpStatusCode.InternalServerError)
  .OrResult(HttpStatusCode.BadGateway)
 
// 在一個策略中同時處理異常和返回值
HttpStatusCode[] httpStatusCodesWorthRetrying = {
   HttpStatusCode.RequestTimeout, // 408
   HttpStatusCode.InternalServerError, // 500
   HttpStatusCode.BadGateway, // 502
   HttpStatusCode.ServiceUnavailable, // 503
   HttpStatusCode.GatewayTimeout // 504
}; 
HttpResponseMessage result = await Policy
  .Handle<HttpRequestException>()
  .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
  .RetryAsync(...)
  .ExecuteAsync( /* Func<Task<HttpResponseMessage>> */ )

第二步:指定策略應如何處理這些錯誤

重試(Retry)
// 重試一次
Policy
  .Handle<SomeExceptionType>()
  .Retry()

// 重試屢次
Policy
  .Handle<SomeExceptionType>()
  .Retry(3)

// 重試屢次,每次重試觸發事件(參數爲這次異常和當前重試次數)
Policy
    .Handle<SomeExceptionType>()
    .Retry(3, onRetry: (exception, retryCount) =>
    {
        // do something 
    });

// 重試屢次,每次重試觸發事件(參數爲這次異常、當前重試次數和當前執行的上下文)
Policy
    .Handle<SomeExceptionType>()
    .Retry(3, onRetry: (exception, retryCount, context) =>
    {
        // do something 
    });
不斷重試直到成功(Retry forever until succeeds)
// 不斷重試
Policy
  .Handle<SomeExceptionType>()
  .RetryForever()

// 不斷重試,每次重試觸發事件(參數爲這次異常) 
Policy
  .Handle<SomeExceptionType>()
  .RetryForever(onRetry: exception =>
  {
        // do something       
  });

// 不斷重試,每次重試觸發事件(參數爲這次異常和當前執行的上下文)
Policy
  .Handle<SomeExceptionType>()
  .RetryForever(onRetry: (exception, context) =>
  {
        // do something       
  });
等待並重試(Wait and retry)

WaitAndRetry策略處理HTTP狀態代碼429的重試後狀態緩存

// 重試屢次, 每次重試之間等待指定的持續時間。(失敗以後觸發等待, 而後再進行下一次嘗試。)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  });

// 重試並觸發事件屢次, 每次重試之間等待指定的持續時間。(事件參數爲當前異常和時間間隔)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan) => {
    // do something    
  }); 

// 重試並觸發事件屢次, 每次重試之間等待指定的持續時間。(事件參數爲當前異常、時間間隔和當前執行的上下文)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan, context) => {
    // do something    
  });

// 重試並觸發事件屢次, 每次重試之間等待指定的持續時間。(事件參數爲當前異常、時間間隔、當前重試次數和當前執行的上下文)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan, retryCount, context) => {
    // do something    
  });

// 重試指定的次數, 根據當前重試次數計算等待時間 (容許指數回退)
// 當前這種狀況下, 等待時間爲:
//  2 ^ 1 = 2 s
//  2 ^ 2 = 4 s
//  2 ^ 3 = 8 s
//  2 ^ 4 = 16 s
//  2 ^ 5 = 32 s
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(5, retryAttempt => 
	TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 
  );

// 重試指定的次數,每次重試時觸發事件,根據當前重試次數計算等待時間。(事件參數爲當前異常、時間間隔和當前執行的上下文)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(
    5, 
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 
    (exception, timeSpan, context) => {
      // do something
    }
  );

// 重試指定的次數,每次重試時觸發事件,根據當前重試次數計算等待時間。(事件參數爲當前異常、時間間隔、當前重試次數和當前執行的上下文)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(
    5, 
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 
    (exception, timeSpan, retryCount, context) => {
      // do something
    }
  );
不斷等待並重試直到成功(Wait and retry forever until succeeds)

若是全部重試都失敗, 重試策略將從新引起最後一個異常返回到調用代碼。安全

// 不斷等待並重試
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetryForever(retryAttempt => 
	TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
    );

// 不斷等待並重試,每次重試時觸發事件。(事件參數爲當前異常、時間間隔)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetryForever(
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),    
    (exception, timespan) =>
    {
        // do something       
    });


// 不斷等待並重試,每次重試時觸發事件。(事件參數爲當前異常、時間間隔和當前執行的上下文)
Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetryForever(
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),    
    (exception, timespan, context) =>
    {
        // do something       
    });
斷路器(Circuit-breaker)

斷路器策略經過在程序出錯時拋出BrokenCircuitException來屏蔽其餘異常。文檔 請注意, 斷路器策略將從新引起全部異常, 甚至是已處理的異常。因此使用時一般會將重試策略和斷路器策略組合使用服務器

// 在指定數量的連續異常後斷開程序執行並在以後的一段時間內保持程序執行斷開。
Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1));

// 在指定數量的連續異常後斷開程序執行並在以後的一段時間內保持程序執行斷開。當程序執行斷開或者從新啓用時觸發事件。(程序執行斷開事件參數爲當前異常和間隔時間,從新啓用事件無參數)
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// 在指定數量的連續異常後斷開程序執行並在以後的一段時間內保持程序執行斷開。當程序執行斷開或者從新啓用時觸發事件。(程序執行斷開事件參數爲當前異常、間隔時間和當前執行上下文,從新啓用事件參數爲當前執行上下文)
Action<Exception, TimeSpan, Context> onBreak = (exception, timespan, context) => { ... };
Action<Context> onReset = context => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// 程序運行狀態, 運行情況。
CircuitState state = breaker.CircuitState;

/*
CircuitState.Closed - 斷路器未觸發,容許操做執行。
CircuitState.Open - 斷路器開啓,阻止操做執行。
CircuitState.HalfOpen - 斷路器開啓指定時間後從新關閉,此狀態容許操做執行,以後的開啓或關閉取決於繼續執行的結果。
CircuitState.Isolated - 斷路器被主動開啓,阻止操做執行。
*/

// 手動打開 (並保持打開) 斷路器(例如須要主動隔離下游服務時)
breaker.Isolate(); 
// 重置斷路器爲關閉狀態, 再次開始容許操做執行。
breaker.Reset(); 
高級斷路器(Advanced Circuit Breaker)
// 在採樣持續時間內, 若是已處理異常的操做的比例超過故障閾值且該時間段內經過請求操做數達到最小吞吐量,主動啓動斷路器。

Policy
    .Handle<SomeExceptionType>()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5, // 當>=50%的操做會致使已處理的異常時中斷程序。
        samplingDuration: TimeSpan.FromSeconds(10), // 採樣時間區間爲10秒
        minimumThroughput: 8, // ... 在採樣時間區間內進行了至少8次操做。
        durationOfBreak: TimeSpan.FromSeconds(30) // 斷路30秒.
                );

// 採用狀態更改委託的配置重載一樣可用於高級斷路器。

// 電路狀態監控和手動控制一樣也可用於高級斷路器。

更多相關資料請參考: 文檔併發

有關斷路器模式的更多信息, 請參見:框架

回退策略(Fallback)
// 執行錯誤時提供替代值。
Policy
   .Handle<Whatever>()
   .Fallback<UserAvatar>(UserAvatar.Blank)

// 執行錯誤時使用回調函數提供替代值。
Policy
   .Handle<Whatever>()
   .Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) // where: public UserAvatar GetRandomAvatar() { ... }

// 執行錯誤時提供替代值的同時觸發事件。(事件參數爲當前異常信息和當前運行上下文)
Policy
   .Handle<Whatever>()
   .Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) => 
    {
        // do something
    });

第三步:執行策略

// 執行操做
var policy = Policy
              .Handle<SomeExceptionType>()
              .Retry();

policy.Execute(() => DoSomething());

// 執行傳遞任意上下文數據的操做
var policy = Policy
    .Handle<SomeExceptionType>()
    .Retry(3, (exception, retryCount, context) =>
    {
        var methodThatRaisedException = context["methodName"];
		Log(exception, methodThatRaisedException);
    });

policy.Execute(
	() => DoSomething(),
	new Dictionary<string, object>() {{ "methodName", "some method" }}
);

// 執行返回結果的函數
var policy = Policy
              .Handle<SomeExceptionType>()
              .Retry();

var result = policy.Execute(() => DoSomething());

// 執行傳遞任意上下文數據且返回結果的操做
var policy = Policy
    .Handle<SomeExceptionType>()
    .Retry(3, (exception, retryCount, context) =>
    {
        object methodThatRaisedException = context["methodName"];
        Log(exception, methodThatRaisedException)
    });

var result = policy.Execute(
    () => DoSomething(),
    new Dictionary<string, object>() {{ "methodName", "some method" }}
);

// 綜合使用
Policy
  .Handle<SqlException>(ex => ex.Number == 1205)
  .Or<ArgumentException>(ex => ex.ParamName == "example")
  .Retry()
  .Execute(() => DoSomething());

爲了簡單起見, 上面的示例顯示了策略定義, 而後是策略執行。 可是在代碼庫和應用程序生命週期中, 策略定義和執行可能一樣常常被分離。 例如, 能夠選擇在啓動時定義策略, 而後經過依賴注入將其提供給使用點。

故障處理(主動策略)

主動策略添加了不基於當策略被引起或返回時才處理錯誤的彈性策略。

第一步:配置

超時(Timeout)
樂觀超時(Optimistic timeout)

樂觀超時經過 CancellationToken 運行, 並假定您執行支持合做取消的委託。您必須使用 Execute/Async(...) 重載以獲取 CancellationToken, 而且執行的委託必須遵照該 CancellationToken

// 若是執行的委託還沒有完成,在調用30秒後超時並返回 。 樂觀超時: 委託應採起並遵照 CancellationToken。
Policy
  .Timeout(30)

// 使用 TimeSpan 配置超時。
Policy
  .Timeout(TimeSpan.FromMilliseconds(2500))

// 經過方法提供可變的超時。
Policy
  .Timeout(() => myTimeoutProvider)) // Func<TimeSpan> myTimeoutProvider

// 超時後觸發事件。(事件參數爲當前執行上下文、執行間隔、當前執行的TASK)
Policy
  .Timeout(30, onTimeout: (context, timespan, task) => 
    {
        // do something 
    });

// 示例:在超時後記錄日誌
Policy
  .Timeout(30, onTimeout: (context, timespan, task) => 
    {
        logger.Warn($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds.");
    });

// 示例:在超時任務完成時捕獲該任務中的任何異常
Policy
  .Timeout(30, onTimeout: (context, timespan, task) => 
    {
        task.ContinueWith(t => {
            if (t.IsFaulted) logger.Error($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds, with: {t.Exception}.");
        });
    });

示例執行:

Policy timeoutPolicy = Policy.TimeoutAsync(30);
HttpResponseMessage httpResponse = await timeoutPolicy
    .ExecuteAsync(
      async ct => await httpClient.GetAsync(endpoint, ct), // 執行一個有參數且響應 CancellationToken 的委託。
      CancellationToken.None // 在這種狀況下, CancellationToken.None 將被傳遞到執行中, 這代表您沒有將指望的令牌控制經過超時策略添加。 自定義 CancellationToken 也能夠經過,詳情請參閱 wiki 中的例子。
      );
悲觀超時(Pessimistic timeout)

悲觀超時容許調用代碼 "離開" 等待執行完成的委託, 即便它不支持取消。 在同步執行中, 這是以犧牲一個額外的線程爲代價的。有關更多細節, 請參見文檔。 示例執行:

Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Pessimistic);
var response = await timeoutPolicy
    .ExecuteAsync(
      async () => await FooNotHonoringCancellationAsync(), // 執行不接受取消令牌且不響應取消的委託。
      );

超時策略在發生超時時引起 TimeoutRejectedException。 更多詳情參見文檔

隔板(Bulkhead)
// 經過該策略將執行限制爲最多12個併發操做。
Policy
  .Bulkhead(12)

// 將經過策略執行的操做限制爲最多12個併發操做, 若是插槽都被佔滿, 最多能夠有兩個操做被等待執行。
Policy
  .Bulkhead(12, 2)

// 限制併發執行, 若是執行被拒絕, 則調用觸發事件。(事件參數爲當前執行上下文)
Policy
  .Bulkhead(12, context => 
    {
        // do something 
    });

// 查看隔板可用容量, 例如健康負荷。
var bulkhead = Policy.Bulkhead(12, 2);
// ...
int freeExecutionSlots = bulkhead.BulkheadAvailableCount;
int freeQueueSlots     = bulkhead.QueueAvailableCount;

當隔板策略的插槽所有被正在執行的操做佔盡是,會引起 BulkheadRejectedException。 更多詳情參見文檔

緩存(Cache)
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var memoryCacheProvider = new MemoryCacheProvider(memoryCache);
var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5));

// .NET Core CacheProviders DI 示例 請參照如下文章  https://github.com/App-vNext/Polly/wiki/Cache#working-with-cacheproviders :
// - https://github.com/App-vNext/Polly.Caching.MemoryCache
// - https://github.com/App-vNext/Polly.Caching.IDistributedCache 

// 定義天天午夜絕對過時的緩存策略。
var cachePolicy = Policy.Cache(memoryCacheProvider, new AbsoluteTtl(DateTimeOffset.Now.Date.AddDays(1));

// 定義超時過時的緩存策略: 每次使用緩存項時, 項目的有效期爲5分鐘。
var cachePolicy = Policy.Cache(memoryCacheProvider, new SlidingTtl(TimeSpan.FromMinutes(5));

// 定義緩存策略, 並捕獲任何緩存提供程序錯誤以進行日誌記錄。
var cachePolicy = Policy.Cache(myCacheProvider, TimeSpan.FromMinutes(5), 
   (context, key, ex) => { 
       logger.Error($"Cache provider, for key {key}, threw exception: {ex}."); // (for example) 
   }
);

// 以直通緩存的身份執行緩存: 首先檢查緩存;若是未找到, 請執行基礎委託並將結果存儲在緩存中。 
// 用於特定執行的緩存的鍵是經過在傳遞給執行的上下文實例上設置操做鍵 (v6 以前: 執行鍵) 來指定的。使用下面顯示的窗體的重載 (或包含相同元素的更豐富的重載)。
// 示例: "fookey" 是將在下面的執行中使用的緩存密鑰。
TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey"));

有關使用其餘緩存提供程序的更豐富的選項和詳細信息, 請參閱:文檔

策略包裝(PolicyWrap)
// 定義由之前定義的策略構建的組合策略。
var policyWrap = Policy
  .Wrap(fallback, cache, retry, breaker, timeout, bulkhead);
// (包裝策略執行任何被包裝的策略: fallback outermost ... bulkhead innermost)
policyWrap.Execute(...)

// 定義標準的彈性策略
PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);

// ... 而後包裝在額外的策略特定於一個請求類型:
Avatar avatar = Policy
   .Handle<Whatever>()
   .Fallback<Avatar>(Avatar.Blank)
   .Wrap(commonResilience)
   .Execute(() => { /* get avatar */ });

// 共享通用彈性, 但將不一樣的策略包裝在另外一個請求類型中:
Reputation reps = Policy
   .Handle<Whatever>()
   .Fallback<Reputation>(Reputation.NotAvailable)
   .Wrap(commonResilience)
   .Execute(() => { /* get reputation */ });  

更多詳情參見文檔

無策略(NoOp)
// 定義一個策略, 該策略將簡單地致使傳遞給執行的委託 "按原樣" 執行。
// 適用於在單元測試中或在應用程序中可能須要策略, 但您只是但願在沒有策略干預的狀況下經過執行的應用程序。
NoOpPolicy noOp = Policy.NoOp();

更多詳情參見文檔

第二步:執行策略

同上

執行後:捕獲結果或任何最終異常

使用 ExecuteAndCapture(...) 方法能夠捕獲執行的結果: 這些方法返回一個執行結果實例, 該實例描述的是成功執行仍是錯誤。

var policyResult = await Policy
              .Handle<HttpRequestException>()
              .RetryAsync()
              .ExecuteAndCaptureAsync(() => DoSomethingAsync());
/*              
policyResult.Outcome - 調用是成功仍是失敗         
policyResult.FinalException - 最後一個異常。若是調用成功, 則捕獲的最後一個異常將爲 null
policyResult.ExceptionType - 定義爲要處理的策略的最後一個異常 (如上面的 HttpRequestException) 或未處理的異常  (如 Exception). 若是調用成功, 則爲 null。
policyResult.Result - 若是執行 func, 調用成功則返回執行結果, 不然爲類型的默認值
*/

處理返回值和 Policy<TResult>

如步驟1b 所述, 從 polly v4.3.0 開始, 策略能夠組合處理返回值和異常:

// 在一個策略中處理異常和返回值
HttpStatusCode[] httpStatusCodesWorthRetrying = {
   HttpStatusCode.RequestTimeout, // 408
   HttpStatusCode.InternalServerError, // 500
   HttpStatusCode.BadGateway, // 502
   HttpStatusCode.ServiceUnavailable, // 503
   HttpStatusCode.GatewayTimeout // 504
}; 
HttpResponseMessage result = await Policy
  .Handle<HttpRequestException>()
  .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
  .RetryAsync(...)
  .ExecuteAsync( /* some Func<Task<HttpResponseMessage>> */ )

要處理的異常和返回結果能夠以任意順序流暢的表達。

強類型 Policy<TResult>

配置策略 .HandleResult<TResult>(...) 或.OrResult<TResult>(...) 生成特定強類型策略 Policy<TResult>,例如 Retry<TResult>, AdvancedCircuitBreaker<TResult>。 這些策略必須用於執行返回 TResult 的委託, 即:

  • Execute(Func<TResult>) (and related overloads)
  • ExecuteAsync(Func<CancellationToken, Task<TResult>>) (and related overloads)

ExecuteAndCapture<TResult>()

.ExecuteAndCapture(...) 在非泛型策略上返回具備屬性的 PolicyResult:

policyResult.Outcome - 調用是成功仍是失敗         
policyResult.FinalException - 最後一個異常。若是調用成功, 則捕獲的最後一個異常將爲 null
policyResult.ExceptionType - 定義爲要處理的策略的最後一個異常 (如上面的 HttpRequestException) 或未處理的異常  (如 Exception). 若是調用成功, 則爲 null。
policyResult.Result - 若是執行 func, 調用成功則返回執行結果, 不然爲類型的默認值

.ExecuteAndCapture<TResult>(Func<TResult>)在強類型策略上添加了兩個屬性:

policyResult.FaultType - 最終的故障是處理異常仍是由策略處理的結果?若是委託執行成功, 則爲 null。
policyResult.FinalHandledResult - 處理的最終故障結果;若是調用成功將爲空或類型的默認值。

Policy<TResult>策略的狀態更改事件

在僅處理異常的非泛型策略中, 狀態更改事件 (如 onRetry 和 onBreak ) 提供 Exception 參數。 在處理 TResult 返回值的通用性策略中, 狀態更改委託是相同的, 除非它們採用 DelegateResult 參數代替異常。DelegateResult 具備兩個屬性:

  • Exception // 若是策略正在處理異常則爲則剛剛引起異常(不然爲空),
  • Result // 若是策略正在處理結果則爲剛剛引起的 TResult (不然爲 default(TResult))

BrokenCircuitException<TResult>

非通用的循環斷路器策略在斷路時拋出一個BrokenCircuitException。此 BrokenCircuitException 包含最後一個異常 (致使中斷的異常) 做爲 InnerException。 關於 CircuitBreakerPolicy<TResult> 策略:

  • 因爲異常而中斷將引起一個 BrokenCircuitException, 並將 InnerException 設置爲觸發中斷的異常 (如之前同樣)。
  • 因爲處理結果而中斷會引起 'BrokenCircuitException<TResult>', 其 Result 屬性設置爲致使電路中斷的結果.

Policy Keys 與 Context data

// 用擴展方法 WithPolicyKey() 使用 PolicyKey 識別策略, 
// (例如, 對於日誌或指標中的相關性)

var policy = Policy
    .Handle<DataAccessException>()
    .Retry(3, onRetry: (exception, retryCount, context) =>
       {
           logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.ExecutionKey}, due to: {exception}.");
       })
    .WithPolicyKey("MyDataAccessPolicy");

// 在上下文中傳遞 ExecutionKey , 並使用 ExecutionKey 標識呼叫站點
var customerDetails = policy.Execute(myDelegate, new Context("GetCustomerDetails"));

// "MyDataAccessPolicy" -> context.PolicyKey 
// "GetCustomerDetails  -> context.ExecutionKey


// 將其餘自定義信息從調用站點傳遞到執行上下文中 
var policy = Policy
    .Handle<DataAccessException>()
    .Retry(3, onRetry: (exception, retryCount, context) =>
       {
           logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.ExecutionKey}, getting {context["Type"]} of id {context["Id"]}, due to: {exception}.");
       })
    .WithPolicyKey("MyDataAccessPolicy");

int id = ... // 客戶id
var customerDetails = policy.Execute(context => GetCustomer(id), 
    new Context("GetCustomerDetails", new Dictionary<string, object>() {{"Type","Customer"},{"Id",id}}

更多資料參考文檔

PolicyRegistry

// 建立策略註冊表 (例如在應用程序啓動時) 
PolicyRegistry registry = new PolicyRegistry();

// 使用策略填充註冊表
registry.Add("StandardHttpResilience", myStandardHttpResiliencePolicy);
// 或者:
registry["StandardHttpResilience"] = myStandardHttpResiliencePolicy;

// 經過 DI 將註冊表實例傳遞給使用站點
public class MyServiceGateway 
{
    public void MyServiceGateway(..., IReadOnlyPolicyRegistry<string> registry, ...)
    {
       ...
    } 
}
// (或者, 若是您更喜歡環境上下文模式, 請使用線程安全的單例)

// 使用註冊表中的策略
registry.Get<IAsyncPolicy<HttpResponseMessage>>("StandardHttpResilience")
    .ExecuteAsync<HttpResponseMessage>(...)

策略註冊表具備一系列進一步的相似字典的語義, 例如 .ContainsKey(...), .TryGet (...), .Count, .Clear(), 和 Remove(...),適用於 v5.2.0 以上版本

有關詳細信息, 請參閱: 文檔

.NET Core 使用Polly重試機制

    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));
        }
        public async Task<IHttpActionResult> Get()
        {
            var httpClient = new HttpClient();
            var requestEndpoint = "http://www.baidu.com";

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

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

            return Ok(numbers);
        }
    }


相關文章
相關標籤/搜索