.Net Core- Polly 的使用实践

在Polly中,有一个重要的概念:Policy,策略有“故障定义”和“故障恢复”两部分组成。故障是指异常、非预期的返回值等情况,而动作则包括重试(Retry)、熔断/断路器(Circuit-Breaker)、Fallback(降级)等。


一、故障定义

1、指定希望策略处理的异常/错误

//单一异常类型
Policy
    .Handle<HttpRequestException>();

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

//多个异常类型
Policy
    .Handle<HttpRequestException>()
    .Or<OperationCanceledException>();

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

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

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

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

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

二、故障恢复

1、重试(Retry)(响应策略)

  • 重试
//重试一次
Policy
  .Handle<HttpRequestException>()
  .Retry();

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

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

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

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

//永远重试,在每次重试时调用一个动作
//提供给Execute()的当前异常和上下文
Policy
  .Handle<HttpRequestException>()
  .RetryForever(onRetry: (exception, Context) =>
  {
      //在每次重试(如日志记录)之前添加要执行的逻辑    
  });
  • 等待并重试
//重试,在每次重试之间等待指定的时间。
//(等待是用来抓住失败,然后再进行下一次尝试的。)
Policy
  .Handle<HttpRequestException>()
  .WaitAndRetry(new[]
  {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(3)
  });

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

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

//重试,在每次重试之间等待指定的时间,
//使用当前异常在每次重试时调用一个操作,
//持续时间、重试计数和提供给Execute()的上下文
Policy
  .Handle<HttpRequestException>()
  .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<HttpRequestException>()
  .WaitAndRetry(5, retryAttempt =>
    TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
  );

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

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

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

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

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

//在指定数目的连续异常后断开电路
//保持电路在规定的时间内断开。
Policy
    .Handle<HttpRequestException>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1));

//在指定数目的连续异常后断开电路
//保持电路在规定的时间内断开,
//调用改变电路状态的动作。
Action<Exception, TimeSpan> onBreak = (exception, timespan) => { };
Action onReset = () => {  };
CircuitBreakerPolicy breaker = Policy
    .Handle<HttpRequestException>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak, onReset);

//在指定数目的连续异常后断开电路
//保持电路在规定的时间内断开,
//要求改变电路状态,
//传递一个提供给Execute()的上下文。
Action<Exception, TimeSpan, Context> onBreak1 = (exception, timespan, context) => { };
Action<Context> onReset1 = context => { };
CircuitBreakerPolicy breaker1 = Policy
    .Handle<HttpRequestException>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1), onBreak1, onReset1);

// 监控电路状态,例如健康报告。
CircuitState state = breaker.CircuitState;


//手动开启(并保持开启状态)断路器-例如手动隔离下游服务。
breaker.Isolate();
//将断路器复位至关闭状态,重新开始接受动作。
breaker.Reset();

先进的断路器

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

Policy
    .Handle<HttpRequestException>()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5, // 分段上> = 50%的动作导致处理的异常... 
        samplingDuration: TimeSpan.FromSeconds(10), // ...在任何10秒的时间段内
        minimumThroughput: 8, // ...在10秒的时间段内提供了至少8个动作。
        durationOfBreak: TimeSpan.FromSeconds(30) // 中断30秒。
                );//接受状态更改委托的配置重载//可按上述断路器的要求提供。//电路状态监测和手动控制//可按上述断路器的要求提供。

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

// 如果执行出错,提供一个替代值。
Policy
    .Handle<HttpRequestException>()
    .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<string> services = new List<string> { "localhost:3003", "localhost:3004" };
        public int serviceIndex = 0;
        public HttpClient client = new HttpClient();

        public Task<string> 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<HttpRequestException>().Or<TimeoutException>().Or<TimeoutRejectedException>()
     .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<HttpRequestException>().Or<TimeoutException>().Or<TimeoutRejectedException>()
       .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<string>.Handle<Exception>()
       .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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值