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

【微服務學習】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)

分享創造快樂