Polly-故障处理库
介绍
Polly是一个.NET弹性和瞬态故障处理库,允许开发人员以流畅和线程安全的方式表达策略,如重试、断路器、超时、舱壁隔离和回退
Polly的七种策略
- 重试
出现故障自动重试 - 隔离
当系统的一处出现故障时,可能促发多个失败的调用,很容易耗尽主机的资源(如 CPU)。下游系统出现故障可能导致上游的故障的调用,甚至可能蔓延到导致系统崩溃。所以要将可控的操作限制在一个固定大小的资源池中,以隔离有潜在可能相互影响的操作。 - 断路器
连续出现N次异常,熔断几秒,等待的这段时间会抛出BrokenCircuitException异常。等待时间结束再执行Excute的时候如果又错了(一次就够),那么继续熔断一段时间,否则恢复正常。熔断的目的是防止给系统造成更大压力 - 超时
在等待一定的时间后,没有返回相应的结果,保证程序不会一直等待下去 - 缓存
针对相同的请求,在第一次访问的时候将响应的数据进行缓存,再次访问的时候直接在缓存中提供响应的数据 - 回退
当程序发生失败的情况的时候,我们将做些什么,定义一个在程序发生失败的时候要执行的动作。比如:短信服务,假设最佳的是调用联通接口,但联通调用失败,我们退而求其次,降级调用移动的,移动的也失败,那么我们就返回失败响应了. - 策略组合
Polly针对不同的故障有不同的策略,我们可以灵活的组合策略,上述的六种策略可以灵活组合使用
Policy知识体系
Nuget包
官方:Polly
微软封装:Microsoft.Extensions.Http.Polly
指定故障
故障可以是Exception,也可以是特定的返回结果
Handle()
:指定Policy希望策略处理的异常
//单个异常、不加条件
Policy.Handle<Exception>();
//单个异常、加条件
Policy.Handle<Exception>(ex => ex.Message == "参数错误");
//多个异常、不加条件
Policy.Handle<ArgumentException>().Or<NullReferenceException>();
//多个异常、加条件
Policy.Handle<ArgumentException>(ex => ex.Message == "参数错误").Or<NullReferenceException>(ex => ex.Message == "参数错误");
HandleResult()
:指定Policy希望处理的委托返回结果
//返回类型为HttpResponseMessage,并且StatusCode == HttpStatusCode.BadGateway
Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)
案例
回退(FallBack)
当主程序发生错误时我们启动备用程序进行处理
案例1、没返回值
Policy.Handle<ArgumentException>()
.Fallback(() => { Console.WriteLine("有异常"); })
.Execute(()=> { throw new ArgumentException("参数异常"); });
案例2、有返回值
HttpResponseMessage response = Policy.Handle<Exception>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)
.Fallback(() => new HttpResponseMessage(HttpStatusCode.BadRequest))
.Execute(() => new HttpResponseMessage(HttpStatusCode.BadGateway));
案例3、获取异常
Policy policy = Policy.Handle<ArgumentException>() //故障
.Fallback(() =>
{
//降级执行的动作
Console.WriteLine("我是降级后的执行的操作");
}, ex =>
{
Console.WriteLine($"业务报错信息为:{ex.Message}");
});
policy.Execute(() =>
{
//执行业务代码
Console.WriteLine("开始任务");
throw new ArgumentException("类型转换失败");
Console.WriteLine("结束任务");
});
重试(Retry)
案例1、重试3次
Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)
.Retry(3)
.Execute(ExecuteMockRequest);
案例2、重试3次,输出重试日志
Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadGateway)
// 2. 指定重试次数和重试策略
.Retry(3, (result, retryCount, context) =>
{
var exception = result.Exception;
var response = result.Result;
Console.WriteLine($"开始第 {retryCount} 次重试:");
})
.Execute(ExecuteMockRequest);
案例3、一直重试
ISyncPolicy policy = Policy
.Handle<Exception>()
.RetryForever()//一直试
案例4、间隔1s、5s、10s、20s、1min各重试一次
Polly.Policy.Handle<WebException>().WaitAndRetry(
new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(20),
TimeSpan.FromMinutes(1)
},
(ex, ts, i, context) =>{Console.WriteLine("出错");}
);
案例5、重试3次,每次等待2秒
ISyncPolicy policy = Policy
.Handle<Exception>()
.WaitAndRetry(3, i => TimeSpan.FromSeconds(2));
超时
案例1、超时1秒抛出异常
try
{
Policy
.Timeout(1, Polly.Timeout.TimeoutStrategy.Pessimistic)
.Execute<HttpResponseMessage>(ExecuteMockRequest);
}
catch (Polly.Timeout.TimeoutRejectedException ex)
{
Console.WriteLine("超时");
}
超时策略应该和重试、回退等策略组合使用
断路器
当发生了故障的时候,则重试了5次还是有故障(代码中的6代表的是在执行短路保护策略之前允许6次故障),那么就停止服务10s钟,10s之后再允许重试
执行结果如下图所示:出现了6次故障之后,直接给我们抛出了短路保护的异常
ISyncPolicy policy = Policy.Handle<Exception>()
.CircuitBreaker(6, TimeSpan.FromSeconds(10));
while (true)
{
try
{
policy.Execute(() =>
{
Console.WriteLine("Job Start");
throw new Exception("Business Error");
});
}
catch (Polly.CircuitBreaker.BrokenCircuitException brokenEx)
{
Console.WriteLine("熔断异常:",brokenEx.InnerException.Message);
}
catch (Exception ex)
{
Console.WriteLine("业务异常 : " + ex.Message);
}
Thread.Sleep(500);
}
高级断路器:
Policy.Handle<Exception>()
.AdvancedCircuitBreakerAsync(
//备注:20秒内,请求次数达到10次以上,失败率达到20%后开启断路器,断路器一旦被打开至少要保持5秒钟的打开状态。
failureThreshold: 0.2D, //失败率达到20%熔断开启
minimumThroughput: 10, //最多调用10次
samplingDuration: TimeSpan.FromSeconds(20), //评估故障持续时长20秒
durationOfBreak: TimeSpan.FromSeconds(5), //恢复正常使用前电路保持打开状态的最少时长5秒
onBreak: (exception, breakDelay, ctx) => //断路器打开时触发事件,程序不能使用
{
var ex = exception.InnerException ?? exception;
this.Logger.LogError($"{key} => 进入打开状态,中断持续时长:{breakDelay},错误类型:{ex.GetType().Name},信息:{ex.Message}");
},
onReset: ctx => //断路器关闭状态触发事件,断路器关闭
{
this.Logger.LogInformation($"{key} => 进入关闭状态,程序恢复正常使用");
},
onHalfOpen: () => //断路器进入半打开状态触发事件,断路器准备再次尝试操作执行
{
this.Logger.LogInformation($"{key} => 进入半开状态,重新尝试接收请求");
}
)
Wrap:策略封装
就是把多个ISyncPolicy合并到一起执行
案例1、当执行超时就回退(超时策略、回退策略混合使用)
ISyncPolicy fallBackPolicy = Policy.Handle<Polly.Timeout.TimeoutRejectedException>()
.Fallback(() =>
{
Console.WriteLine("Fallback");
});
ISyncPolicy policyTimeout = Policy.Timeout(1, Polly.Timeout.TimeoutStrategy.Pessimistic);
ISyncPolicy mainPolicy = Policy.Wrap(fallBackPolicy, policyTimeout);
mainPolicy.Execute(() =>
{
Console.WriteLine("Job Start...");
Thread.Sleep(2000);
});
案例2、当执行超时,重试3次,然后回退(超时策略、回退策略、重试策略混合使用)
ISyncPolicy fallBackPolicy = Policy.Handle<Polly.Timeout.TimeoutRejectedException>()
.Fallback(() =>
{
Console.WriteLine("Fallback");
});
ISyncPolicy retryPolicy = Policy.Handle<Polly.Timeout.TimeoutRejectedException>().Retry(3);
ISyncPolicy policyTimeout = Policy.Timeout(1, Polly.Timeout.TimeoutStrategy.Pessimistic);
ISyncPolicy mainPolicy = Policy.Wrap(fallBackPolicy, retryPolicy, policyTimeout);
mainPolicy.Execute(() =>
{
Console.WriteLine("Job Start...");
Thread.Sleep(2000);
});
案例3、熔断+降级:Execute执行业务代码无须再用Try-catch包裹,否则不抛异常,则无法降级,我们这里演示的是降级,并在降级中拿到业务代码的异常信息
{
//3.1 熔断
Policy policyCBreaker = Policy.Handle<Exception>()
.CircuitBreaker(3, TimeSpan.FromSeconds(10)); //连续出错3次之后熔断10秒(不会再去尝试执行业务代码)。
//3.2 降级
Policy policyFallback = Policy.Handle<Exception>()
.Fallback(() =>
{
//降级执行的动作
Console.WriteLine("我是降级后的执行的操作");
}, ex =>
{
//这里是捕获业务代码中的错误,业务代码中就不要再写try-catch,否则不抛异常,则无法降级
Console.WriteLine($"业务报错信息为:{ex.Message}");
});
//3.4 包裹
Policy policy = policyFallback.Wrap(policyCBreaker);
//3.4 执行业务
while (true)
{
Console.WriteLine("开始Execute");
//try
//{
policy.Execute(() =>
{
Console.WriteLine("-------------------------------------开始任务---------------------------------------");
throw new Exception();
Console.WriteLine("完成任务");
});
//}
// 不要再写try-catch,否则不抛异常,则无法降级
//catch (Exception ex)
//{
// Console.WriteLine("execute出错" + ex.Message);
//}
Thread.Sleep(2000);
}
}
异步
比如在业务代码中有一些Http的调用或者IO操作时,不妨用用异步操作来提高一点效率
public static async void Case5()
{
Policy<byte[]> policy = Policy<byte[]>.Handle<Exception>()
.FallbackAsync(async c =>
{
Console.WriteLine("Executed Error!");
return new byte[0];
}, async r =>
{
Console.WriteLine(r.Exception);
});
policy = policy.WrapAsync(Policy.TimeoutAsync(20, TimeoutStrategy.Pessimistic,
async (context, timespan, task) =>
{
Console.WriteLine("Timeout!");
}));
var bytes = await policy.ExecuteAsync(async ()=>
{
Console.WriteLine("Start Job");
HttpClient httpClient = new HttpClient();
var result = await httpClient.GetByteArrayAsync("https://images2018.cnblogs.com/blog/381412/201806/381412-20180606230929894-145212290.png");
Console.WriteLine("Finish Job");
return result;
});
Console.WriteLine($"Length of bytes : {bytes.Length}");
}
参考:
https://mp.weixin.qq.com/s/p-K3SZrzLuwTirHvT7ijUw
https://www.cnblogs.com/edisonchou/p/9159644.html
https://www.cnblogs.com/xitianqujing/p/13737729.html
https://my.oschina.net/u/3772973/blog/4585842