介绍
本节我们来介绍一款强大的库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 "无异常时函数"; } }