跳转到主内容
极星编程网:以代码为星,赴技术山海!

.Net Core- Polly 的使用实践

在Polly中,有一个重要的概念:Policy,策略有“ 故障定义 ”和“ 故障恢复 ”两部分组成。

故障是指异常、非预期的返回值等情况,而动作则包括重试(Retry)、熔断/断路器(Circuit-Breaker)、Fallback(降级)等。

Polly 一、故障定义 1、指定希望策略处理的异常/错误 2、指定要处理的返回结果 二、故障恢复 1、重试(Retry)(响应策略) 2、熔断/断路器(Circuit Breaker)(响应策略) 3、降级/回退(Fallback)(响应策略) 4、超时(Timeout)(主动策略) 5、策略包装(PolicyWrap) 三、实践 1、测试访问端口为3003和3004的服务 2、定义超时策略 3、定义重试策略 4、定义熔断策略 5、定义降级策略 6、包装策略(以上策略一起异步使用) 7、结果展示: 一、故障定义 1、指定希望策略处理的异常/错误

//单一异常类型

Policy .Handle();

//带条件的单一异常类型(条件过滤) Policy .Handle(ex => ex.Number == 1205);

//多个异常类型 Policy .Handle() .Or();

//带有条件的多个异常类型 Policy .Handle(ex => ex.Number == 1205) .Or(ex => ex.ParamName == "example");

2、指定要处理的返回结果

//处理带条件的返回值

Policy .HandleResult(r => r.StatusCode == HttpStatusCode.NotFound);//当返回值为HttpResponseMessage,并且其StatusCode为NotFound时,才对其进行处理。

//处理多个返回值 Policy .HandleResult(r => r.StatusCode == HttpStatusCode.InternalServerError) .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway);

//处理原始返回值(隐含使用.Equals()) Policy .HandleResult(HttpStatusCode.InternalServerError) .OrResult(HttpStatusCode.BadGateway);

二、故障恢复 1、重试(Retry)(响应策略) 重试

//重试一次

Policy .Handle() .Retry();

//多次重试 Policy .Handle() .Retry(3);

//重试多次,在每次重试时调用一个操作 //使用当前异常和重试计数 Policy .Handle() .Retry(3, onRetry: (exception, retryCount) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

//重试多次,在每次重试时调用一个操作 //对于当前的异常,重试计数和上下文 //提供执行() Policy .Handle() .Retry(3, onRetry: (exception, retryCount, context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

永远重试(直到成功)

//永远重试

Policy .Handle() .RetryForever();

//永远重试,在每次重试时调用一个动作 //当前异常 Policy .Handle() .RetryForever(onRetry: exception => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

//永远重试,在每次重试时调用一个动作 //提供给Execute()的当前异常和上下文 Policy .Handle() .RetryForever(onRetry: (exception, Context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

等待并重试

//重试,在每次重试之间等待指定的时间。

//(等待是用来抓住失败,然后再进行下一次尝试的。

) Policy .Handle() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });

//重试,在每次重试之间等待指定的时间, //使用当前异常在每次重试时调用一个操作 // 和持续时间 Policy .Handle() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) }, (exception, timeSpan) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

//重试,在每次重试之间等待指定的时间, //使用当前异常在每次重试时调用一个操作, //为Execute()提供的持续时间和上下文 Policy .Handle() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) }, (exception, timeSpan, context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

//重试,在每次重试之间等待指定的时间, //使用当前异常在每次重试时调用一个操作, //持续时间、重试计数和提供给Execute()的上下文 Policy .Handle() .WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) }, (exception, timeSpan, retryCount, context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

//使用一个函数来重试指定的次数 //计算重试之间等待的时间 //当前的重试尝试(允许指数回退) //这种情况要等一等 // 2 ^ 1 = 2 seconds then // 2 ^ 2 = 4 seconds then // 2 ^ 3 = 8 seconds then // 2 ^ 4 = 16 seconds then // 2 ^ 5 = 32 seconds Policy .Handle() .WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) );

//使用一个函数来重试指定的次数 //计算重试之间等待的时间 //当前的重试尝试,在每次重试时调用一个操作 //提供当前异常、持续时间和上下文 // 执行() Policy .Handle() .WaitAndRetry( 5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timeSpan, context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 } );

//使用一个函数来重试指定的次数 //计算重试之间等待的时间 //当前的重试尝试,在每次重试时调用一个操作 //使用当前异常、持续时间、重试计数和上下文 //提供执行() Policy .Handle() .WaitAndRetry( 5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timeSpan, retryCount, context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 } );

永远等待并重试(直到成功)

// 永远等待并重试

Policy .Handle() .WaitAndRetryForever(retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) );

//永远等待并重试,在每次重试时调用一个动作 //当前异常和等待时间 Policy .Handle() .WaitAndRetryForever( retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timespan) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

//永远等待并重试,在每次重试时调用一个动作 //当前异常、等待时间和提供给Execute()的上下文 Policy .Handle() .WaitAndRetryForever( retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timespan, context) => { //在每次重试(如日志记录)之前添加要执行的逻辑 });

2、熔断/断路器(Circuit Breaker)(响应策略)

//在指定数目的连续异常后断开电路

//保持电路在规定的时间内断开。

Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1));

//在指定数目的连续异常后断开电路 //保持电路在规定的时间内断开, //调用改变电路状态的动作。

Action onBreak = (exception, timespan) => { }; Action onReset = () => { }; CircuitBreakerPolicy breaker = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

//在指定数目的连续异常后断开电路 //保持电路在规定的时间内断开, //要求改变电路状态, //传递一个提供给Execute()的上下文。

Action onBreak1 = (exception, timespan, context) => { }; Action onReset1 = context => { }; CircuitBreakerPolicy breaker1 = Policy .Handle() .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak1, onReset1);

// 监控电路状态,例如健康报告。

CircuitState state = breaker.CircuitState;

//手动开启(并保持开启状态)断路器-例如手动隔离下游服务。

breaker.Isolate(); //将断路器复位至关闭状态,重新开始接受动作。

breaker.Reset();

先进的断路器

// 如果在采样期间内,

//导致处理异常的操作超过故障阈值的比例, //还规定了动作的次数通过电路的周期 //至少是最小吞吐量。

Policy .Handle() .AdvancedCircuitBreaker( failureThreshold: 0.5, // 分段上> = 50%的动作导致处理的异常... samplingDuration: TimeSpan.FromSeconds(10), // ...在任何10秒的时间段内 minimumThroughput: 8, // ...在10秒的时间段内提供了至少8个动作。

durationOfBreak: TimeSpan.FromSeconds(30) // 中断30秒。

);//接受状态更改委托的配置重载//可按上述断路器的要求提供。

//电路状态监测和手动控制//可按上述断路器的要求提供。

3、降级/回退(Fallback)(响应策略)

// 如果执行出错,提供一个替代值。

Policy .Handle() .Fallback(() => { //代替方法

}, (ex) => { //拦截方法

}).Execute(() => {

});

4、超时(Timeout)(主动策略)

///乐观的超时

//如果执行的委托尚未完成,则超时并在30秒后返回给调用者。

Policy .Timeout(30);

///悲观的超时 // 超时30秒后,如果执行的委托尚未完成。

即使执行的代码没有取消机制,也会强制执行此超时。

Policy .Timeout(30, TimeoutStrategy.Pessimistic);

5、策略包装(PolicyWrap)

// 定义一个组合策略,由以前定义的策略构建。

var policyWrap = Policy .Wrap(Fallback, cache, retry, breaker, timeout, bulkhead); // (将策略包装在任何已执行的委托周围:回退最外层…舱壁内层的) policyWrap.Execute(() => {

});

三、实践 1、NuGet安装:Polly 2、添加引用 using Polly; using Polly.CircuitBreaker; using Polly.Timeout; 1、测试访问端口为3003和3004的服务

public class Test {

public List services = new List { "localhost:3003", "localhost:3004" }; public int serviceIndex = 0; public HttpClient client = new HttpClient();

public Task HttpInvokeAsync() { if (serviceIndex >= services.Count) { serviceIndex = 0; } var service = services[serviceIndex++]; Console.WriteLine(DateTime.Now.ToString() + "开始服务:" + service); return client.GetStringAsync("http://" + service + "/api/weatherforecast"); } }

2、定义超时策略

var timeoutPolicy = Policy.TimeoutAsync(1, (context, timespan, task) =>

{ Console.WriteLine("超时了,抛出TimeoutRejectedException异常。

"); return Task.CompletedTask; });

3、定义重试策略

var retryPolicy = Policy.Handle().Or().Or()

.WaitAndRetryAsync( retryCount: 2, sleepDurationProvider: retryAttempt => { var waitSeconds = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)); Console.WriteLine(DateTime.Now.ToString() + "----------------重试策略:[" + retryAttempt + "], 等待 " + waitSeconds + "秒!"); return waitSeconds; });

4、定义熔断策略

var circuitBreakerPolicy = Policy.Handle().Or().Or()

.CircuitBreakerAsync( // 熔断前允许出现几次错误 exceptionsAllowedBeforeBreaking: 2, // 熔断时间 durationOfBreak: TimeSpan.FromSeconds(3), // 熔断时触发 onBreak: (ex, breakDelay) => { Console.WriteLine(DateTime.Now.ToString() + "----------------断路器->断路 " + breakDelay.TotalMilliseconds + "毫秒! 异常: ", ex.Message); }, // 熔断恢复时触发 onReset: () => { Console.WriteLine(DateTime.Now.ToString() + "----------------断路器->好了! 再次闭合电路."); }, // 在熔断时间到了之后触发 onHalfOpen: () => { Console.WriteLine(DateTime.Now.ToString() + "----------------断路器->半开,下一个呼叫是测试."); } );

5、定义降级策略

var fallbackPolicy = Policy.Handle()

.FallbackAsync( fallbackValue: "替代数据", onFallbackAsync: (exception, context) => { Console.WriteLine("降级策略, 异常->" + exception.Exception.Message + ", 返回替代数据."); return Task.CompletedTask; });

6、包装策略(以上策略一起异步使用)

//循环执行50次,策略的执行是非常简单的,唯一需要注意的就是调用的顺序:如下是依次从右到左进行调用,

//首先是进行超时的判断,一旦超时就触发TimeoutRejectedException异常, //然后就进入到了重试策略中,如果重试了一次就成功了,那就直接返回,不再触发其他策略, //否则就进入到熔断策略中: Task.Run(async () => { for (int i = 0; i < 50; i++) { Console.WriteLine(DateTime.Now.ToString() + "----------------开始第[" + i + "]-----------------------------"); var res = await fallbackPolicy .WrapAsync(Policy.WrapAsync(circuitBreakerPolicy, retryPolicy, timeoutPolicy)) .ExecuteAsync(() => new Test().HttpInvokeAsync()); Console.WriteLine(DateTime.Now.ToString() + "--------------- 开始[" + i + "]->响应" + ": Ok->" + res); await Task.Delay(1000); Console.WriteLine("--------------------------------------------------------------------------------------------------------------------"); } });

7、结果展示:

参考链接: https://www.cnblogs.com/RainingNight/p/circuitbreaker-polly-in-asp-net-core.html 官网GitHub地址:https://github.com/App-vNext/Polly#retry

相关文章