介紹
本節我們來介紹一款強大的庫Polly,Polly是一種.NET彈性和瞬態故障處理庫,允許我們以非常順暢和線程安全的方式來執諸如行重試,斷路,超時,故障恢復等策略。 Polly針對對.NET 4.0,.NET 4.5和.NET Standard 1.1以及.NET Core實現,該項目作者現已成爲.NET基金會一員,項目一直在不停迭代和更新,項目地址【https://github.com/App-vNext/Polly】,你值得擁有。接下來我們以.NET Framework 4.5來演示它的強大功能。
簡單的例子
using System; namespace Polly { class Program { static void Main(string[] args) { // 這個例子展示了當執行的時候如果遇到RedisLockException的異常則會進行重試調用。 var policy = Policy .Handle<RedisLockException>() // 定義條件 .Retry(); // 定義處理方式 // 執行 policy.Execute(() => { Console.WriteLine("異常之前"); throw new RedisLockException("create lock failed"); Console.WriteLine("異常之後"); } ); } } public class RedisLockException : Exception { public RedisLockException(string message) : base(message) { Console.WriteLine(message); } } }
定義條件
// 單個異常類型 var policy1 = Policy .Handle<RedisLockException>() // 定義條件 .Retry(); // 定義處理方式 // 限定條件的單個異常 異常和表達式都要符合 var policy2 = Policy .Handle<RedisLockException>(ex => ex.Message == "wolf") .Retry(); // 定義處理方式 // 執行 policy2.Execute(() => { Console.WriteLine("異常之前"); throw new RedisLockException("wolf"); Console.WriteLine("異常之後"); } ); // 多個異常類型 Policy .Handle<HttpRequestException>() .Or<OperationCanceledException>() .Retry(); // 定義處理方式 // 限定條件的多個異常 Policy .Handle<SqlException>(ex => ex.Number == 1205) .Or<ArgumentException>(ex => ex.ParamName == "example") .Retry(); // 定義處理方式 // Inner Exception 異常裏面的異常類型 Policy .HandleInner<HttpRequestException>() .OrInner<OperationCanceledException>(ex => ex.CancellationToken != new System.Threading.CancellationToken()) .Retry(); // 定義處理方式
以及用返回結果來限定
// 返回結果加限定條件 Policy .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.NotFound) // 處理多個返回結果 Policy .HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError) .OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway) // 處理元類型結果 (用.Equals) Policy .HandleResult<HttpStatusCode>(HttpStatusCode.InternalServerError) .OrResult<HttpStatusCode>(HttpStatusCode.BadGateway) // 在一個policy裏面同時處理異常和返回結果。 HttpStatusCode[] httpStatusCodesWorthRetrying = { HttpStatusCode.RequestTimeout, // 408 HttpStatusCode.InternalServerError, // 500 HttpStatusCode.BadGateway, // 502 HttpStatusCode.ServiceUnavailable, // 503 HttpStatusCode.GatewayTimeout // 504 }; HttpResponseMessage result = Policy .Handle<HttpRequestException>() .OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode)) .RetryAsync(...) .ExecuteAsync( /* some Func<Task<HttpResponseMessage>> */ )
重試策略(Retry)
重試策略針對的前置條件是短暫的故障延遲且在短暫的延遲之後能夠自我糾正。允許我們做的是能夠自動配置重試機制。
按次數重試
using System; namespace Polly { class Program { static void Main(string[] args) { // 重試1次 Policy .Handle<RedisLockException>() .Retry().Execute(() => { throw new RedisLockException("13"); }); //// 重試3(N)次 Policy .Handle<RedisLockException>() .Retry(3).Execute(() => { throw new RedisLockException("13"); }); ; //// 重試多次,加上重試時的action參數 Policy .Handle<RedisLockException>() .Retry(3, (exception, retryCount) => { Console.WriteLine(exception.Message); Console.WriteLine(retryCount); }).Execute(() => { throw new RedisLockException("13"); }); } } public class RedisLockException : Exception { public RedisLockException(string message) : base(message) { Console.WriteLine(message); } } }
不斷重試
using System; namespace Polly { class Program { static void Main(string[] args) { // 不斷重試,直到成功 Policy .Handle<RedisLockException>() .RetryForever().Execute(() => { }); // 不斷重試,帶action參數在每次重試的時候執行 Policy .Handle<RedisLockException>() .RetryForever(exception => { // do something }).Execute(() => { }); } } public class RedisLockException : Exception { public RedisLockException(string message) : base(message) { Console.WriteLine(message); } } }
等待之後重試
using System; namespace Polly { class Program { static void Main(string[] args) { // 重試3次,分別等待10、20、30秒。 Policy .Handle<RedisLockException>() .WaitAndRetry(new[] { TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(30) }).Execute(() => { throw new RedisLockException("1234"); }); } } public class RedisLockException : Exception { public RedisLockException(string message) : base(message) { Console.WriteLine(message); } } }
熔斷
熔斷也可以被作爲當遇到某種錯誤場景下的一個操作。以下代碼展示了當發生2次RedisLockException的異常的時候則會熔斷1分鐘,該操作後續如果繼續嘗試執行則會直接返回錯誤 。
Policy .Handle<SomeExceptionType>() .CircuitBreaker(2, TimeSpan.FromMinutes(1));
回退(Fallback)
操作仍然會失敗,也就是說當發生這樣的事情時我們打算做什麼。也就是說定義失敗返回操作。
class Program { static void Main(string[] args) { try { var fallBackPolicy = Policy<string> .Handle<DivideByZeroException>() .Fallback("執行失敗,返回Fallback"); //運行異常時,設置默認值 var fallBack = fallBackPolicy.Execute(Compute); Console.WriteLine(fallBack); } catch (DivideByZeroException e) { Console.WriteLine($"Excuted Failed,Message: ({e.Message})"); } Console.Read(); } static string Compute() { var a = 0; a = 1 / a; return "無異常時函數"; } }