本文基本是官方說明的翻譯和總結(https://github.com/App-vNext/Polly)
什么是Polly?
Polly是一款基於.NET的彈性及瞬間錯誤處理庫, 它允許開發人員以順暢及線程安全的方式執行重試(Retry),斷路器(Circuit),超時(Timeout),隔板隔離(Bulkhead Isolation)及后背策略(Fallback)。
Polly適用於.NET 4.0, .NET 4.5及.NET Standard 1.1(覆蓋.NET Core, Mono, Xamarin.IOS, Xamarin.Android, UWP, WP 8.1+)。
安裝Polly
.NET 4.0版本
Install-Package Polly.Net40Async
.NET 4.5及以上版本, .Net Standard 1.1
Install-Package Polly
彈性策略
Polly提供多種彈性策略。
重試策略
前提
程序會產生許多瞬時故障,但是在一定時間延遲之后,程序會自動糾正故障。
實現效果
允許配置自動重試。
斷路器策略
前提
當系統發生嚴重故障時,快速響應請求失敗比讓用戶等待要好。
避免故障系統過載有助於恢復系統。
實現效果
當系統錯誤超過預配置的數量,系統將斷路一段時間。
超時策略
前提
超出一定時間的等待,想要得到正確的結果是不太可能的。
實現效果
保證調用者不需要等待超時。
隔板隔離
前提
當進程出現故障,多個失敗的請求很容易占滿服務器資源(線程/CPU)。
一個處於故障狀態的下游系統,也會導致其上游系統故障。
實現效果
將嚴格管控故障進程,使其使用固定大小的資源池,隔離他們對其他進程的潛在影響
緩存策略
前提
一定比例的請求可能是相似的。
實現效果
從緩存中提供已知的響應。
當第一次讀取的時候,將響應自動緩存起來。
后備策略
前提
當故障依然存在的時候,你打算做什么。
實現效果
當程序依然發生故障時,執行指定操作。
包裝策略
前提
不同的故障需要不同的策略。包裝策略即組合策略。
實現效果
允許靈活的將以上任意幾種策略組合在一起。
如何使用Polly進行故障/異常處理?
Polly處理故障/異常有以下幾個步驟。
- 指定處理的異常/故障類型
- [可選] 指定處理的異常返回值
- 指定處理策略
- 執行策略
指定處理異常/故障的類型
Polly使用Policy類的泛型方法Handle指定Polly需要處理異常/故障的類型。
指定單個異常類型
Policy.Handle<DivideByZeroException>()
指定帶條件的異常類型
Policy.Handle<SqlException>(ex => ex.Number == 1205)
Polly也支持指定多種異常/故障類型, 這里需要使用Or方法
Policy.Handle<DivideByZeroException>().Or<ArgumentException>()
指定多個帶條件的異常類型
Policy
.Handle<SqlException>(ex =ex.Number == 1205)
.Or<ArgumentException>(ex =ex.ParamName == "example")
Polly也支持指定內部異常
Policy
.HandleInner<HttpResponseException>()
.OrInner<OperationCanceledException>(ex => ex.CancellationToken == myToken)
指定處理的異常返回值
Polly除了支持處理異常/故障類型,還支持處理異常返回值。所謂的處理異常結果,就是當Polly監控的方法,返回某些特定結果時, Polly會觸發異常/故障處理策略。
Polly使用Policy類的泛型方法HandleResult制定Polly需要處理的異常結果.
指定觸發異常/故障處理策略的返回值
例如:當某個方法的返回值類型是HttpResposneMessage, 並且返回值的StatusCode是NotFound時,觸發異常/故障處理策略。
Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound)
指定多個返回值
Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)
同時指定異常類型和返回值
HttpStatusCode[] httpStatusCodesWorthRetrying = {
HttpStatusCode.RequestTimeout, // 408
HttpStatusCode.InternalServerError, // 500
HttpStatusCode.BadGateway, // 502
HttpStatusCode.ServiceUnavailable, // 503
HttpStatusCode.GatewayTimeout // 504
};
HttpResponseMessage result = Policy
.Handle<HttpResponseException>()
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
指定異常處理策略
重試策略
重試一次
Policy
.Handle<DivideByZeroException>()
.Retry()
重試多次
Policy
.Handle<DivideByZeroException>()
.Retry(3)
重試多次,每次重試觸發一個行為
Policy
.Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount) =>
{
// do something
});
永久重試(直到成功)
永久重試
Policy
.Handle<DivideByZeroException>()
.RetryForever()
永久重試,每次重試觸發一個行為
Policy
.Handle<DivideByZeroException>()
.RetryForever(exception =>
{
// do something
});
等待並重試
指定每個重試的間隔時間
Policy
.Handle<DivideByZeroException>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
});
在這個例子如果第一次出現異常,會在1秒后重試,如果依然出現異常,會在再次出現異常后2秒繼續重試,以此類推,下次異常后3秒繼續重試
每次重試,觸發一個行為
Policy
.Handle<DivideByZeroException>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}, (exception, timeSpan) => {
// do something
});
斷路器策略
在發生指定次數的異常/故障之后,斷開回路
Policy
.Handle<DivideByZeroException>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
發生2次異常之后,斷開回路1分鍾
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { ... };
Action onReset = () => { ... };
CircuitBreakerPolicy breaker = Policy
.Handle<DivideByZeroException>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);
發生2次異常之后,斷開回路1分鍾, 在觸發斷路時觸發onBreak方法,當重置斷路器時,觸發onReset方法
后備策略
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank)
當程序觸發異常/故障后,返回一個備用值
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(() => UserAvatar.GetRandomAvatar())
當程序觸發異常/故障后,使用一個方法返回一個備用值
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) =>
{
// do something
});
當程序觸發異常/故障后,返回一個備用值,並觸發一個方法
Policy
.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank, onFallback: (exception, context) =>
{
// do something
});
執行策略
Polly將監控DoSomething方法,如果發生DivideByZeroException異常,就使用重試策略
var policy = Policy
.Handle<DivideByZeroException>()
.Retry();
policy.Execute(() => DoSomething());
向Polly上下文中傳遞任意值
var policy = Policy
.Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount, context) =>
{
var methodThatRaisedException = context["methodName"];
Log(exception, methodThatRaisedException);
});
policy.Execute(
() => DoSomething(),
new Dictionary<string, object>() {{ "methodName", "some method" }}
);