歡迎光臨
每天分享高質量文章

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

何為熔斷降級

  “熔斷器如同電力過載保護器。它可以實現快速失敗,如果它在一段時間內偵測到許多類似的錯誤,會強迫其以後的多個呼叫快速失敗,不再訪問遠端伺服器,從而防止應用程式不斷地嘗試執行可能會失敗的操作,使得應用程式繼續執行而不用等待修正錯誤,或者浪費時間去等到長時間的超時產生。”   降級的目的是當某個服務提供者發生故障的時候,向呼叫方傳回一個替代響應。

  簡單一句話概括,降級就是在呼叫的下游服務A出現問題(常見超時),提供PLAN-B,傳回的效果可能沒有服務A好,但是聊勝於無。而熔斷器的存在就是要保障何時走到降級方法,何時恢復,以什麼樣的策略恢復。

.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提供了一些可用的舊版本,具體版本對應如下:

  該專案作者現已成為.NET基金會一員,專案一直在不停迭代和更新,專案地址【https://github.com/App-vNext/Polly】。

七種恢復策略

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

保護系統故障免受過載,Polly可以幫其恢復。

“痛了,自然就會放下”

“讓它歇一下”

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

這兩個風險都將造成嚴重的後果。

“一顆老鼠屎壞了一鍋湯” 將受管制的操作限制在固定的資源池中,避免其他資源受其影響。
快取(Cache)
(快速開始 ; 深入學習)
資料不會很頻繁的進行更新,相同請求的響應是相似的。 “聽說
你還會再來
我翹首以盼”
首次載入資料時將響應資料進行快取,請求時若快取中存在則直接從快取中讀取。
回退(Fallback)
(快速開始 ; 深入學習)
操作將仍然失敗 – 但是你可以實現準備好失敗後要做的補救措施。 “你若安好,我備胎到老。” 定義失敗時要傳回 (或要執行的操作) 的替代值。.
策略包裝(PolicyWrap)
(快速開始 ; 深入學習)
不同的故障需要不同的策略,也就意味著彈性靈活使用組合。 “謀定而後動” 允許靈活地組合上述任何策略。

實踐

故障處理(被動策略)

故障處理策略處理透過策略執行的程式碼所引發的特定的異常或傳回結果。

第一步:指定希望處理的異常(可選-指定要處理的傳回結果)

指定希望處理的異常:
// 單一異常種類
Policy
  .Handle()

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

// 多種異常
Policy
  .Handle()
  .Or()

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

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

從Polly v4.3.0起,包含傳回TResult的呼叫的策略也可以處理TResult傳回值

// 帶條件判斷的單種傳回值處理
Policy
  .HandleResult(r => r.StatusCode == HttpStatusCode.NotFound)

// 帶條件判斷的多種傳回值處理
Policy
  .HandleResult(r => r.StatusCode == HttpStatusCode.InternalServerError)
  .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)

// 原始傳回值處理 (隱式呼叫 .Equals())
Policy
  .HandleResult(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()
  .OrResult(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
  .RetryAsync(...)
  .ExecuteAsync( /* Func> */ )

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

重試(Retry)
// 重試一次
Policy
  .Handle()
  .Retry()

// 重試多次
Policy
  .Handle()
  .Retry(3)

// 重試多次,每次重試觸發事件(引數為此次異常和當前重試次數)
Policy
    .Handle()
    .Retry(3, onRetry: (exception, retryCount) =>
    {
        // do something
    });

// 重試多次,每次重試觸發事件(引數為此次異常、當前重試次數和當前執行的背景關係)
Policy
    .Handle()
    .Retry(3, onRetry: (exception, retryCount, context) =>
    {
        // do something
    });
不斷重試直到成功(Retry forever until succeeds)
// 不斷重試
Policy
  .Handle()
  .RetryForever()

// 不斷重試,每次重試觸發事件(引數為此次異常)
Policy
  .Handle()
  .RetryForever(onRetry: exception =>
  {
        // do something
  });

// 不斷重試,每次重試觸發事件(引數為此次異常和當前執行的背景關係)
Policy
  .Handle()
  .RetryForever(onRetry: (exception, context) =>
  {
        // do something
  });
等待並重試(Wait and retry)

WaitAndRetry策略處理HTTP狀態程式碼429的重試後狀態

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

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

// 重試並觸發事件多次, 每次重試之間等待指定的持續時間。(事件引數為當前異常、時間間隔和當前執行的背景關係)
Policy
  .Handle()
  .WaitAndRetry(new[]
  {
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(2),
    TimeSpan.FromSeconds(3)
  }, (exception, timeSpan, context) => {
    // do something
  });

// 重試並觸發事件多次, 每次重試之間等待指定的持續時間。(事件引數為當前異常、時間間隔、當前重試次數和當前執行的背景關係)
Policy
  .Handle()
  .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()
  .WaitAndRetry(5, retryAttempt =>
	TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
  );

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

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

如果所有重試都失敗, 重試策略將重新引發最後一個異常傳回到呼叫程式碼。

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

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


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

斷路器策略透過在程式出錯時丟擲BrokenCircuitException來遮蔽其他異常。檔案 請註意, 斷路器策略將重新引發所有異常, 甚至是已處理的異常。所以使用時通常會將重試策略和斷路器策略組合使用

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

// 在指定數量的連續異常後斷開程式執行併在之後的一段時間內保持程式執行斷開。當程式執行斷開或者重新啟用時觸發事件。(程式執行斷開事件引數為當前異常和間隔時間,重新啟用事件無引數)
Action onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

// 在指定數量的連續異常後斷開程式執行併在之後的一段時間內保持程式執行斷開。當程式執行斷開或者重新啟用時觸發事件。(程式執行斷開事件引數為當前異常、間隔時間和當前執行背景關係,重新啟用事件引數為當前執行背景關係)
Action onBreak = (exception, timespan, context) => { ... };
Action onReset = context => { ... };
CircuitBreakerPolicy breaker = Policy
    .Handle()
    .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()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5, // 當>=50%的操作會導致已處理的異常時中斷程式。
        samplingDuration: TimeSpan.FromSeconds(10), // 取樣時間區間為10秒
        minimumThroughput: 8, // ... 在取樣時間區間內進行了至少8次操作。
        durationOfBreak: TimeSpan.FromSeconds(30) // 斷路30秒.
                );

// 採用狀態更改委託的配置多載同樣可用於高階斷路器。

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

更多相關資料請參考: 檔案

有關斷路器樣式的更多資訊, 請參見:

  • 改造 Netflix API 增加介面彈性
  • 斷路器淺談 (馬丁·福勒)
  • 斷路器樣式 (Microsoft)
  • 原始斷路器鏈
回退策略(Fallback)
// 執行錯誤時提供替代值。
Policy
   .Handle()
   .Fallback(UserAvatar.Blank)

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

// 執行錯誤時提供替代值的同時觸發事件。(事件引數為當前異常資訊和當前執行背景關係)
Policy
   .Handle()
   .Fallback(UserAvatar.Blank, onFallback: (exception, context) =>
    {
        // do something
    });

第三步:執行策略

// 執行操作
var policy = Policy
              .Handle()
              .Retry();

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

// 執行傳遞任意背景關係資料的操作
var policy = Policy
    .Handle()
    .Retry(3, (exception, retryCount, context) =>
    {
        var methodThatRaisedException = context["methodName"];
		Log(exception, methodThatRaisedException);
    });

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

// 執行傳回結果的函式
var policy = Policy
              .Handle()
              .Retry();

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

// 執行傳遞任意背景關係資料且傳回結果的操作
var policy = Policy
    .Handle()
    .Retry(3, (exception, retryCount, context) =>
    {
        object methodThatRaisedException = context["methodName"];
        Log(exception, methodThatRaisedException)
    });

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

// 綜合使用
Policy
  .Handle(ex => ex.Number == 1205)
  .Or(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 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()
   .Fallback(Avatar.Blank)
   .Wrap(commonResilience)
   .Execute(() => { /* get avatar */ });

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

更多詳情參見檔案

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

更多詳情參見檔案

第二步:執行策略

同上

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

使用 ExecuteAndCapture(…) 方法可以捕獲執行的結果: 這些方法傳回一個執行結果實體, 該實體描述的是成功執行還是錯誤。

var policyResult = await Policy
              .Handle()
              .RetryAsync()
              .ExecuteAndCaptureAsync(() => DoSomethingAsync());
/*
policyResult.Outcome - 呼叫是成功還是失敗
policyResult.FinalException - 最後一個異常。如果呼叫成功, 則捕獲的最後一個異常將為 null
policyResult.ExceptionType - 定義為要處理的策略的最後一個異常 (如上面的 HttpRequestException) 或未處理的異常  (如 Exception). 如果呼叫成功, 則為 null。
policyResult.Result - 如果執行 func, 呼叫成功則傳回執行結果, 否則為型別的預設值
*/

處理傳回值和 Policy

如步驟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()
  .OrResult(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
  .RetryAsync(...)
  .ExecuteAsync( /* some Func> */ )

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

強型別 Policy

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

  • Execute(Func) (and related overloads)
  • ExecuteAsync(Func>) (and related overloads)

ExecuteAndCapture()

.ExecuteAndCapture(…) 在非泛型策略上傳回具有屬性的 PolicyResult:

policyResult.Outcome - 呼叫是成功還是失敗
policyResult.FinalException - 最後一個異常。如果呼叫成功, 則捕獲的最後一個異常將為 null
policyResult.ExceptionType - 定義為要處理的策略的最後一個異常 (如上面的 HttpRequestException) 或未處理的異常  (如 Exception). 如果呼叫成功, 則為 null。
policyResult.Result - 如果執行 func, 呼叫成功則傳回執行結果, 否則為型別的預設值

.ExecuteAndCapture(Func)在強型別策略上添加了兩個屬性:

policyResult.FaultType - 最終的故障是處理異常還是由策略處理的結果?如果委託執行成功, 則為 null。
policyResult.FinalHandledResult - 處理的最終故障結果;如果呼叫成功將為空或型別的預設值。

Policy策略的狀態更改事件

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

  • Exception // 如果策略正在處理異常則為則剛剛引發異常(否則為空),
  • Result // 如果策略正在處理結果則為剛剛引發的 TResult (否則為 default(TResult))

BrokenCircuitException

非通用的迴圈斷路器策略在斷路時丟擲一個BrokenCircuitException。此 BrokenCircuitException 包含最後一個異常 (導致中斷的異常) 作為 InnerException。關於 CircuitBreakerPolicy 策略:

  • 由於異常而中斷將引發一個 BrokenCircuitException, 並將 InnerException 設定為觸發中斷的異常 (如以前一樣)。
  • 由於處理結果而中斷會引發 ‘BrokenCircuitException‘, 其 Result 屬性設定為導致電路中斷的結果.

Policy Keys 與 Context data

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

var policy = Policy
    .Handle()
    .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()
    .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() {{"Type","Customer"},{"Id",id}}

更多資料參考檔案

PolicyRegistry

// 建立策略登錄檔 (例如在應用程式啟動時)
PolicyRegistry registry = new PolicyRegistry();

// 使用策略填充登錄檔
registry.Add("StandardHttpResilience", myStandardHttpResiliencePolicy);
// 或者:
registry["StandardHttpResilience"] = myStandardHttpResiliencePolicy;

// 透過 DI 將登錄檔實體傳遞給使用站點
public class MyServiceGateway
{
    public void MyServiceGateway(..., IReadOnlyPolicyRegistry registry, ...)
    {
       ...
    }
}
// (或者, 如果您更喜歡環境背景關係樣式, 請使用執行緒安全的單例)

// 使用登錄檔中的策略
registry.Get>("StandardHttpResilience")
    .ExecuteAsync(...)

策略登錄檔具有一系列進一步的類似字典的語意, 例如 .ContainsKey(…), .TryGet(…), .Count, .Clear(), 和 Remove(…),適用於 v5.2.0 以上版本

有關詳細資訊, 請參閱: 檔案

.NET Core 使用Polly重試機制

    public class PollyController : ApiController
    {
        public readonly RetryPolicy _httpRequestPolicy;
        public PollyController()
        {
            _httpRequestPolicy = Policy.HandleResult(
            r => r.StatusCode == HttpStatusCode.InternalServerError)
            .WaitAndRetryAsync(3,
            retryAttempt => TimeSpan.FromSeconds(retryAttempt));
        }
        public async Task Get()
        {
            var httpClient = new HttpClient();
            var requestEndpoint = "http://www.baidu.com";

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

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

            return Ok(numbers);
        }
    }

已同步到看一看
贊(0)

分享創造快樂