【微服务学习】Polly:熔断降级组件
何為熔斷降級
“熔斷器如同電力過載保護器。它可以實現(xiàn)快速失敗,如果它在一段時間內偵測到許多類似的錯誤,會強迫其以后的多個調用快速失敗,不再訪問遠程服務器,從而防止應用程序不斷地嘗試執(zhí)行可能會失敗的操作,使得應用程序繼續(xù)執(zhí)行而不用等待修正錯誤,或者浪費時間去等到長時間的超時產生。” 降級的目的是當某個服務提供者發(fā)生故障的時候,向調用方返回一個替代響應。
簡單一句話概括,降級就是在調用的下游服務A出現(xiàn)問題(常見超時),提供PLAN-B,返回的效果可能沒有服務A好,但是聊勝于無。而熔斷器的存在就是要保障何時走到降級方法,何時恢復,以什么樣的策略恢復。
.NET Core 熔斷降級實踐
簡介
Polly是一種.NET彈性和瞬態(tài)故障處理庫,允許我們以非常順暢和線程安全的方式來執(zhí)諸如行重試,斷路,超時,故障恢復等策略。
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提供了一些可用的舊版本,具體版本對應如下:
該項目作者現(xiàn)已成為.NET基金會一員,項目一直在不停迭代和更新,項目地址【https://github.com/App-vNext/Polly】。
七種恢復策略
| 重試策略(Retry)? (policy family) (快速開始?;?深入學習) | 重試策略針對的前置條件是短暫的故障延遲且在短暫的延遲之后能夠自我糾正。 | "也許這只是曇花一現(xiàn)" | 允許我們做的是能夠自動配置重試機制。 |
| 斷路器(Circuit-breaker) (policy family) (快速開始?;?深入學習) | 斷路器策略針對的前置條件是當系統(tǒng)繁忙時,快速響應失敗總比讓用戶一直等待更好。 保護系統(tǒng)故障免受過載,Polly可以幫其恢復。 | "痛了,自然就會放下" "讓它歇一下" | 當故障超過某個預先配置的閾值時, 中斷電路 (塊執(zhí)行) 一段時間。 |
| 超時(Timeout) (快速開始?;?深入學習) | 超時策略針對的前置條件是超過一定的等待時間,想要得到成功的結果是不可能的。 | "你不必等待,她不會再來" | 保證調用者不必等待太長時間。 |
| 隔板隔離(Bulkhead Isolation) (快速開始?;?深入學習) | 隔板隔離針對的前置條件是當進程出現(xiàn)故障時,多個失敗一直在主機中對資源(例如線程/ CPU)一直占用。下游系統(tǒng)故障也可能導致上游失敗。 這兩個風險都將造成嚴重的后果。 | "一顆老鼠屎壞了一鍋湯" | 將受管制的操作限制在固定的資源池中,避免其他資源受其影響。 |
| 緩存(Cache) (快速開始?;?深入學習) | 數(shù)據(jù)不會很頻繁的進行更新,相同請求的響應是相似的。 | "聽說? 你還會再來 我翹首以盼" | 首次加載數(shù)據(jù)時將響應數(shù)據(jù)進行緩存,請求時若緩存中存在則直接從緩存中讀取。 |
| 回退(Fallback) (快速開始?;?深入學習) | 操作將仍然失敗 - 但是你可以實現(xiàn)準備好失敗后要做的補救措施。 | "你若安好,我備胎到老。" | 定義失敗時要返回 (或要執(zhí)行的操作) 的替代值。. |
| 策略包裝(PolicyWrap) (快速開始?;?深入學習) | 不同的故障需要不同的策略,也就意味著彈性靈活使用組合。 | "謀定而后動" | 允許靈活地組合上述任何策略。 |
實踐
故障處理(被動策略)
故障處理策略處理通過策略執(zhí)行的代碼所引發(fā)的特定的異常或返回結果。
第一步:指定希望處理的異常(可選-指定要處理的返回結果)
指定希望處理的異常:
// 單一異常種類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返回值
// 帶條件判斷的單種返回值處理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)
// 重試多次,每次重試觸發(fā)事件(參數(shù)為此次異常和當前重試次數(shù))
Policy
.Handle<SomeExceptionType>()
.Retry(3, onRetry: (exception, retryCount) =>
{
// do something
});
// 重試多次,每次重試觸發(fā)事件(參數(shù)為此次異常、當前重試次數(shù)和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.Retry(3, onRetry: (exception, retryCount, context) =>
{
// do something
});
不斷重試直到成功(Retry forever until succeeds)
// 不斷重試Policy
.Handle<SomeExceptionType>()
.RetryForever()
// 不斷重試,每次重試觸發(fā)事件(參數(shù)為此次異常)
Policy
.Handle<SomeExceptionType>()
.RetryForever(onRetry: exception =>
{
// do something
});
// 不斷重試,每次重試觸發(fā)事件(參數(shù)為此次異常和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.RetryForever(onRetry: (exception, context) =>
{
// do something
});
等待并重試(Wait and retry)
WaitAndRetry策略處理HTTP狀態(tài)代碼429的重試后狀態(tài)
// 重試多次, 每次重試之間等待指定的持續(xù)時間。(失敗之后觸發(fā)等待, 然后再進行下一次嘗試。)Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
});
// 重試并觸發(fā)事件多次, 每次重試之間等待指定的持續(xù)時間。(事件參數(shù)為當前異常和時間間隔)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan) => {
// do something
});
// 重試并觸發(fā)事件多次, 每次重試之間等待指定的持續(xù)時間。(事件參數(shù)為當前異常、時間間隔和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan, context) => {
// do something
});
// 重試并觸發(fā)事件多次, 每次重試之間等待指定的持續(xù)時間。(事件參數(shù)為當前異常、時間間隔、當前重試次數(shù)和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan, retryCount, context) => {
// do something
});
// 重試指定的次數(shù), 根據(jù)當前重試次數(shù)計算等待時間 (允許指數(shù)回退)
// 當前這種情況下, 等待時間為:
// 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))
);
// 重試指定的次數(shù),每次重試時觸發(fā)事件,根據(jù)當前重試次數(shù)計算等待時間。(事件參數(shù)為當前異常、時間間隔和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(
5,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, context) => {
// do something
}
);
// 重試指定的次數(shù),每次重試時觸發(fā)事件,根據(jù)當前重試次數(shù)計算等待時間。(事件參數(shù)為當前異常、時間間隔、當前重試次數(shù)和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetry(
5,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, retryCount, context) => {
// do something
}
);
不斷等待并重試直到成功(Wait and retry forever until succeeds)
如果所有重試都失敗, 重試策略將重新引發(fā)最后一個異常返回到調用代碼。
// 不斷等待并重試Policy
.Handle<SomeExceptionType>()
.WaitAndRetryForever(retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
);
// 不斷等待并重試,每次重試時觸發(fā)事件。(事件參數(shù)為當前異常、時間間隔)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetryForever(
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timespan) =>
{
// do something
});
// 不斷等待并重試,每次重試時觸發(fā)事件。(事件參數(shù)為當前異常、時間間隔和當前執(zhí)行的上下文)
Policy
.Handle<SomeExceptionType>()
.WaitAndRetryForever(
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timespan, context) =>
{
// do something
});
斷路器(Circuit-breaker)
斷路器策略通過在程序出錯時拋出BrokenCircuitException來屏蔽其他異常。文檔 請注意, 斷路器策略將重新引發(fā)所有異常, 甚至是已處理的異常。所以使用時通常會將重試策略和斷路器策略組合使用。
// 在指定數(shù)量的連續(xù)異常后斷開程序執(zhí)行并在之后的一段時間內保持程序執(zhí)行斷開。Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
// 在指定數(shù)量的連續(xù)異常后斷開程序執(zhí)行并在之后的一段時間內保持程序執(zhí)行斷開。當程序執(zhí)行斷開或者重新啟用時觸發(fā)事件。(程序執(zhí)行斷開事件參數(shù)為當前異常和間隔時間,重新啟用事件無參數(shù))
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);
// 在指定數(shù)量的連續(xù)異常后斷開程序執(zhí)行并在之后的一段時間內保持程序執(zhí)行斷開。當程序執(zhí)行斷開或者重新啟用時觸發(fā)事件。(程序執(zhí)行斷開事件參數(shù)為當前異常、間隔時間和當前執(zhí)行上下文,重新啟用事件參數(shù)為當前執(zhí)行上下文)
Action<Exception, TimeSpan, Context> onBreak = (exception, timespan, context) => { ... };
Action<Context> onReset = context => { ... };
CircuitBreakerPolicy breaker = Policy
.Handle<SomeExceptionType>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);
// 程序運行狀態(tài), 運行狀況。
CircuitState state = breaker.CircuitState;
/*
CircuitState.Closed - 斷路器未觸發(fā),允許操作執(zhí)行。
CircuitState.Open - 斷路器開啟,阻止操作執(zhí)行。
CircuitState.HalfOpen - 斷路器開啟指定時間后重新關閉,此狀態(tài)允許操作執(zhí)行,之后的開啟或關閉取決于繼續(xù)執(zhí)行的結果。
CircuitState.Isolated - 斷路器被主動開啟,阻止操作執(zhí)行。
*/
// 手動打開 (并保持打開) 斷路器(例如需要主動隔離下游服務時)
breaker.Isolate();
// 重置斷路器為關閉狀態(tài), 再次開始允許操作執(zhí)行。
breaker.Reset();
高級斷路器(Advanced Circuit Breaker)
// 在采樣持續(xù)時間內, 如果已處理異常的操作的比例超過故障閾值且該時間段內通過請求操作數(shù)達到最小吞吐量,主動啟動斷路器。Policy
.Handle<SomeExceptionType>()
.AdvancedCircuitBreaker(
failureThreshold: 0.5, // 當>=50%的操作會導致已處理的異常時中斷程序。
samplingDuration: TimeSpan.FromSeconds(10), // 采樣時間區(qū)間為10秒
minimumThroughput: 8, // ... 在采樣時間區(qū)間內進行了至少8次操作。
durationOfBreak: TimeSpan.FromSeconds(30) // 斷路30秒.
);
// 采用狀態(tài)更改委托的配置重載同樣可用于高級斷路器。
// 電路狀態(tài)監(jiān)控和手動控制同樣也可用于高級斷路器。
更多相關資料請參考:?文檔
有關斷路器模式的更多信息, 請參見:
改造 Netflix API 增加接口彈性
斷路器淺談 (馬丁·福勒)
斷路器模式 (Microsoft)
原始斷路器鏈
回退策略(Fallback)
// 執(zhí)行錯誤時提供替代值。Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank)
// 執(zhí)行錯誤時使用回調函數(shù)提供替代值。
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar()) // where: public UserAvatar GetRandomAvatar() { ... }
// 執(zhí)行錯誤時提供替代值的同時觸發(fā)事件。(事件參數(shù)為當前異常信息和當前運行上下文)
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) =>
{
// do something
});
第三步:執(zhí)行策略
// 執(zhí)行操作var policy = Policy
.Handle<SomeExceptionType>()
.Retry();
policy.Execute(() => DoSomething());
// 執(zhí)行傳遞任意上下文數(shù)據(jù)的操作
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" }}
);
// 執(zhí)行返回結果的函數(shù)
var policy = Policy
.Handle<SomeExceptionType>()
.Retry();
var result = policy.Execute(() => DoSomething());
// 執(zhí)行傳遞任意上下文數(shù)據(jù)且返回結果的操作
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());
為了簡單起見, 上面的示例顯示了策略定義, 然后是策略執(zhí)行。但是在代碼庫和應用程序生命周期中, 策略定義和執(zhí)行可能同樣經常被分離。例如, 可以選擇在啟動時定義策略, 然后通過依賴注入將其提供給使用點。
故障處理(主動策略)
主動策略添加了不基于當策略被引發(fā)或返回時才處理錯誤的彈性策略。
第一步:配置
超時(Timeout)
樂觀超時(Optimistic timeout)
樂觀超時通過 CancellationToken 運行, 并假定您執(zhí)行支持合作取消的委托。您必須使用 Execute/Async(...)?重載以獲取?CancellationToken, 并且執(zhí)行的委托必須遵守該?CancellationToken。
// 如果執(zhí)行的委托尚未完成,在調用30秒后超時并返回 。樂觀超時: 委托應采取并遵守 CancellationToken。Policy
.Timeout(30)
// 使用 TimeSpan 配置超時。
Policy
.Timeout(TimeSpan.FromMilliseconds(2500))
// 通過方法提供可變的超時。
Policy
.Timeout(() => myTimeoutProvider)) // Func<TimeSpan> myTimeoutProvider
// 超時后觸發(fā)事件。(事件參數(shù)為當前執(zhí)行上下文、執(zhí)行間隔、當前執(zhí)行的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}.");
});
});
示例執(zhí)行:
Policy timeoutPolicy = Policy.TimeoutAsync(30);HttpResponseMessage httpResponse = await timeoutPolicy
.ExecuteAsync(
async ct => await httpClient.GetAsync(endpoint, ct), // 執(zhí)行一個有參數(shù)且響應 CancellationToken 的委托。
CancellationToken.None // 在這種情況下, CancellationToken.None 將被傳遞到執(zhí)行中, 這表明您沒有將期望的令牌控制通過超時策略添加。自定義 CancellationToken 也可以通過,詳情請參閱 wiki 中的例子。
);
悲觀超時(Pessimistic timeout)
悲觀超時允許調用代碼 "離開" 等待執(zhí)行完成的委托, 即使它不支持取消。在同步執(zhí)行中, 這是以犧牲一個額外的線程為代價的。有關更多細節(jié), 請參見文檔。示例執(zhí)行:
Policy timeoutPolicy = Policy.TimeoutAsync(30, TimeoutStrategy.Pessimistic);var response = await timeoutPolicy
.ExecuteAsync(
async () => await FooNotHonoringCancellationAsync(), // 執(zhí)行不接受取消令牌且不響應取消的委托。
);
超時策略在發(fā)生超時時引發(fā)?TimeoutRejectedException。更多詳情參見文檔。
隔板(Bulkhead)
// 通過該策略將執(zhí)行限制為最多12個并發(fā)操作。Policy
.Bulkhead(12)
// 將通過策略執(zhí)行的操作限制為最多12個并發(fā)操作, 如果插槽都被占滿, 最多可以有兩個操作被等待執(zhí)行。
Policy
.Bulkhead(12, 2)
// 限制并發(fā)執(zhí)行, 如果執(zhí)行被拒絕, 則調用觸發(fā)事件。(事件參數(shù)為當前執(zhí)行上下文)
Policy
.Bulkhead(12, context =>
{
// do something
});
// 查看隔板可用容量, 例如健康負荷。
var bulkhead = Policy.Bulkhead(12, 2);
// ...
int freeExecutionSlots = bulkhead.BulkheadAvailableCount;
int freeQueueSlots = bulkhead.QueueAvailableCount;
當隔板策略的插槽全部被正在執(zhí)行的操作占滿是,會引發(fā)?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)
}
);
// 以直通緩存的身份執(zhí)行緩存: 首先檢查緩存;如果未找到, 請執(zhí)行基礎委托并將結果存儲在緩存中。
// 用于特定執(zhí)行的緩存的鍵是通過在傳遞給執(zhí)行的上下文實例上設置操作鍵 (v6 之前: 執(zhí)行鍵) 來指定的。使用下面顯示的窗體的重載 (或包含相同元素的更豐富的重載)。
// 示例: "fookey" 是將在下面的執(zhí)行中使用的緩存密鑰。
TResult result = cachePolicy.Execute(context => getFoo(), new Context("FooKey"));
有關使用其他緩存提供程序的更豐富的選項和詳細信息, 請參閱:文檔
策略包裝(PolicyWrap)
// 定義由以前定義的策略構建的組合策略。var policyWrap = Policy
.Wrap(fallback, cache, retry, breaker, timeout, bulkhead);
// (包裝策略執(zhí)行任何被包裝的策略: 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)
// 定義一個策略, 該策略將簡單地導致傳遞給執(zhí)行的委托 "按原樣" 執(zhí)行。// 適用于在單元測試中或在應用程序中可能需要策略, 但您只是希望在沒有策略干預的情況下通過執(zhí)行的應用程序。
NoOpPolicy noOp = Policy.NoOp();
更多詳情參見文檔
第二步:執(zhí)行策略
同上
執(zhí)行后:捕獲結果或任何最終異常
使用 ExecuteAndCapture(...) 方法可以捕獲執(zhí)行的結果: 這些方法返回一個執(zhí)行結果實例, 該實例描述的是成功執(zhí)行還是錯誤。
var policyResult = await Policy.Handle<HttpRequestException>()
.RetryAsync()
.ExecuteAndCaptureAsync(() => DoSomethingAsync());
/*
policyResult.Outcome - 調用是成功還是失敗
policyResult.FinalException - 最后一個異常。如果調用成功, 則捕獲的最后一個異常將為 null
policyResult.ExceptionType - 定義為要處理的策略的最后一個異常 (如上面的 HttpRequestException) 或未處理的異常 (如 Exception). 如果調用成功, 則為 null。
policyResult.Result - 如果執(zhí)行 func, 調用成功則返回執(zhí)行結果, 否則為類型的默認值
*/
處理返回值和?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>。這些策略必須用于執(zhí)行返回 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 - 如果執(zhí)行 func, 調用成功則返回執(zhí)行結果, 否則為類型的默認值
.ExecuteAndCapture<TResult>(Func<TResult>)在強類型策略上添加了兩個屬性:
policyResult.FaultType - 最終的故障是處理異常還是由策略處理的結果?如果委托執(zhí)行成功, 則為 null。policyResult.FinalHandledResult - 處理的最終故障結果;如果調用成功將為空或類型的默認值。
Policy<TResult>策略的狀態(tài)更改事件
在僅處理異常的非泛型策略中, 狀態(tài)更改事件 (如 onRetry 和 onBreak ) 提供 Exception 參數(shù)。在處理 TResult 返回值的通用性策略中, 狀態(tài)更改委托是相同的, 除非它們采用 DelegateResult參數(shù)代替異常。DelegateResult具有兩個屬性:
Exception?// 如果策略正在處理異常則為則剛剛引發(fā)異常(否則為空),
Result?// 如果策略正在處理結果則為剛剛引發(fā)的 TResult (否則為 default(TResult))
BrokenCircuitException<TResult>
非通用的循環(huán)斷路器策略在斷路時拋出一個BrokenCircuitException。此 BrokenCircuitException 包含最后一個異常 (導致中斷的異常) 作為 InnerException。關于 CircuitBreakerPolicy<TResult>?策略:
由于異常而中斷將引發(fā)一個 BrokenCircuitException, 并將 InnerException 設置為觸發(fā)中斷的異常 (如以前一樣)。
由于處理結果而中斷會引發(fā) '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
// 將其他自定義信息從調用站點傳遞到執(zhí)行上下文中
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
// 創(chuàng)建策略注冊表 (例如在應用程序啟動時)PolicyRegistry registry = new PolicyRegistry();
// 使用策略填充注冊表
registry.Add("StandardHttpResilience", myStandardHttpResiliencePolicy);
// 或者:
registry["StandardHttpResilience"] = myStandardHttpResiliencePolicy;
// 通過 DI 將注冊表實例傳遞給使用站點
public class MyServiceGateway
{
public void MyServiceGateway(..., IReadOnlyPolicyRegistry<string> registry, ...)
{
...
}
}
// (或者, 如果您更喜歡環(huán)境上下文模式, 請使用線程安全的單例)
// 使用注冊表中的策略
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);
}
}
原文地址:https://www.cnblogs.com/WayneShao/p/10672101.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的【微服务学习】Polly:熔断降级组件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在.net core 中PetaPoco
- 下一篇: 为什么不要使用 async void