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

posted @ 2020-02-02 12:15  .Neterr  阅读(590)  评论(0编辑  收藏  举报