.NET 5 中使用 Consul+Ocelot+Polly缓存、限流、熔断、降级

dotNET全栈开发

共 9188字,需浏览 19分钟

 ·

2021-10-26 17:46


一、简介

Polly 是一个 .NET 弹性和瞬态故障处理库,允许开发人员以线程安全的方式来实现重试、断路、超时、隔离和回退策略。

瞬态故障就是可能会出现的,比喻网络不稳定或无法访问,或服务宕机。

二、Ocelot各种策略使用和解释

下面各种策略都是基于前一篇Ocelot+Consul的配置基础上修改。

2.1、Ocelot缓存

缓存能有效提升程序性能

在ocelot.json中增加缓存配置

  {
    "Routes": [
      {
        //转发到下游服务地址--url变量
        "DownstreamPathTemplate""/api/{url}",
        //下游http协议
        "DownstreamScheme""http",
        //负载方式,
        "LoadBalancerOptions": {
          "Type""RoundRobin" // 轮询
        },
        //上游地址
        "UpstreamPathTemplate""/T1/{url}"//网关地址--url变量   //冲突的还可以加权重Priority
        "UpstreamHttpMethod": [ "GET""POST""DELETE""PUT" ],
        "UseServiceDisConvery"true//使用服务发现
        "ServiceName""api"//Consul服务名称
        //缓存设置
        "FileCacheOptions": {
          "TtlSeconds"10//缓存10s(同一个地址请求就返回缓存结果)
          "Region"""//缓存region
        }
      }
    ],
    "GlobalConfiguration": {
      //Ocelot应用地址
      "BaseUrl""http://172.16.2.9:5200",
      "ServiceDiscoveryProvider": {
        //Consul地址
        "Host""172.16.2.84",
        //Consul端口
        "Port"8500,
        "Type""Consul"//由Consul提供服务发现,每次请求Consul
      }
    }
  }

缓存是针对下游地址缓存的,同一个地址请求返回相同数据,所以针对一些不变的数据才能做缓存,根据用户登录信息不同返回不同数据的就不能做了。

测试:访问 http://172.16.2.9:5200/T1/Test/GetName

刷新后还是5201端口数据,说明是从缓存取的

10s后刷新端口变成5202

2.2、Ocelot限流

为什么要限流呢,防止请求过多把程序搞宕机了,也可以有效防止爬虫和ddos攻击,预估出服务的处理能力,然后设置限流,可以限制单位时间内的访问量(失败一部分请求比整个服务挂掉强)。

ocelot.json文件增加限流配置

  {
    "Routes": [
      {
        //转发到下游服务地址--url变量
        "DownstreamPathTemplate""/api/{url}",
        //下游http协议
        "DownstreamScheme""http",
        //负载方式,
        "LoadBalancerOptions": {
          "Type""RoundRobin" // 轮询
        },
        //上游地址
        "UpstreamPathTemplate""/T1/{url}"//网关地址--url变量   //冲突的还可以加权重Priority
        "UpstreamHttpMethod": [ "GET""POST""DELETE""PUT" ],
        "UseServiceDisConvery"true//使用服务发现
        "ServiceName""api"//Consul服务名称
        //限流配置
        "RateLimitOptions": {
          "ClientWhitelist": [ "admin" ], // 白名单
          "EnableRateLimiting"true// 是否启用限流
          "Period""10s"// 统计时间段:1s, 5m, 1h, 1d
          "PeriodTimespan"10// 多少秒之后客户端可以重试
          "Limit"5 // 在统计时间段内允许的最大请求数量
        }
      }
    ],
    "GlobalConfiguration": {
      //Ocelot应用地址
      "BaseUrl""http://172.16.2.9:5200",
      "ServiceDiscoveryProvider": {
        //Consul地址
        "Host""172.16.2.84",
        //Consul端口
        "Port"8500,
        "Type""Consul" //由Consul提供服务发现,每次请求Consul
      },
      //限流
      "RateLimitOptions": {
        "QuotaExceededMessage""errr:request fast!"//限流后响应内容
        "HttpStatusCode"666//http状态码可以自定义     "ClientIdHeader": "client_id" // 用来识别客户端的请求头,默认是 ClientId
      }
    }
  }

这里用Postman来演示比较直观。

可以看到,在10s内请求了5次之后的请求就失败了,返回的状态码是自定义的666,然后等10s过后又恢复访问,上面设置的白名单在Headers加上就可以

不受限流影响,可以无限访问。

2.3、Ocelot+Polly的熔断

安装NuGet包 Ocelot.Provider.Polly。

Startup.cs增加 .AddPolly()

public void ConfigureServices(IServiceCollection services)
{
           services.AddOcelot()
               .AddConsul()
               .AddPolly();
}

Ocelot.Provider.Polly的熔断机制是一个超时和熔断的组合,(Polly有超时策略,熔断策略,这里是2个策略的结合使用,下面Polly策略会说到),所以如果是单单是服务报500异常是触发不了的。

接口超过多长时间进入半熔断状态,返回服务不可用, 连续超过多少次进入熔断状态就直接停掉该请求返回,多长时间再恢复。

修改ocelot.json配置

  //*****************************单地址********************************
  {
    "Routes": [
      {
        //转发到下游服务地址--url变量
        "DownstreamPathTemplate""/api/{url}",
        //下游http协议
        "DownstreamScheme""http",
        //负载方式,
        "LoadBalancerOptions": {
          "Type""RoundRobin" // 轮询
        },
        //上游地址
        "UpstreamPathTemplate""/T1/{url}"//网关地址--url变量   //冲突的还可以加权重Priority
        "UpstreamHttpMethod": [ "GET""POST""DELETE""PUT" ],
        "UseServiceDisConvery"true//使用服务发现
        "ServiceName""api"//Consul服务名称
        //熔断设置
        "QoSOptions": {
          "ExceptionsAllowedBeforeBreaking"3//允许多少个异常请求
          "DurationOfBreak"5000// 熔断的时间5s,单位为ms
          "TimeoutValue"5000 //单位ms,如果下游请求的处理时间超过多少则自如将请求设置为超时 默认90秒
        }
      }
    ],
    "GlobalConfiguration": {
      //Ocelot应用对外地址
      "BaseUrl""http://172.16.2.9:5200",
      "ServiceDiscoveryProvider": {
        //Consul地址
        "Host""172.16.2.84",
        //Consul端口
        "Port"8500,
        "Type""Consul" //由Consul提供服务发现,每次请求Consul
      }
    }
  }

在之前启动的3个服务增加一个抛异常的接口和一个睡眠接口。

 [Route("api/[controller]/[action]")]
 public class TestController : Controller
 {
     private IConfiguration _configuration;
     public TestController(IConfiguration configuration)
     {
         _configuration = configuration;
     }
     public IActionResult GetName()
     {
         string port = _configuration["port"];
         return Json($"端口:{port},姓名:张三");
     }public IActionResult GetSleep()
     {
         string port = _configuration["port"];

         //线程睡眠6s
         Thread.Sleep(6000);
         return Json($"端口:{port},睡眠6s后返回");
     }
 }

访问GetSleep()接口,前三次等待6s返回503,后面访问什么接口都是快速返回503,服务熔断。

三、Polly各种策略使用和解释

上面网关处做了Ocelot+Polly的熔断策略,然后服务链上也是需要做一些策略,这里介绍的是在服务里用Polly做各种常用的策略。

3.1、Polly降级

降级就是当我们指定的代码处理失败时就执行我们备用的代码。

现在在之前的三个服务中加入Polly降级策略

安装NuGet包 --Polly

新建一个OrderController.cs

    [Route("api/[controller]/[action]")]
    public class OrderController : Controller
    {
        private readonly OrderService _orderService;
        public OrderController(OrderService orderService)
        {
            _orderService = orderService;
        }
        public IActionResult CreateOrder()
        {
            string result = _orderService.CreateOrder();
            return Content(result);
        }
    }

新建一个OrderService.cs

  public class OrderService
  {
      private Policy<string> _policy;
      public OrderService()
      {
          //降级
          _policy = Policy<string>
              .Handle() //异常故障
              .Fallback(() =>
              {
                  //降级回调 todo降级后逻辑
                  return "降级后的值";
              });

      }
      public string CreateOrder()
      {
          //用polly执行
          return _policy.Execute(() =>
          {
              //业务逻辑 todo
              Console.WriteLine($"{DateTime.Now},开始处理业务");

              throw new Exception("233出错啦");
              Console.WriteLine("处理完成");
              return "成功啦";
          });
      }
  }

把OrderService注入IOC容器,注意,使用Polly时,实例一定要以单例模式注入,因为如果是每次都执行构造函数给_policy赋一次值,那_policy每次都是全新的,下面的熔断策畋会不生效。

Startup.cs中的ConfigureServices()加上

 public void ConfigureServices(IServiceCollection services)
 {
      services.AddControllersWithViews();
      services.AddControllers().AddJsonOptions(cfg =>
      {
      services.AddSingleton();
 }

测试,访问http://ip:端口/api/Order/CreateOrder

可以看到返回的是降级后处理的值。

3.2、Polly熔断

熔断就是当一处代码报错超过多少次,就让它熔断多长时间再恢复,熔断时Polly会截断请求,不会再进入到具体业务,这能有效减少没必要的业务性能损耗。

把OrderService构建函数处改成

public OrderService()
{
     //熔断
     _policy = Policy<string>.Handle()
     .CircuitBreaker(5, TimeSpan.FromSeconds(10));//连续出错5次后熔断10秒,不会在进到业务代码
}

测试结果:

5次前的返回:

熔断后返回:

3.3、Polly重试

把OrderService的构造函数处修改为:

  public OrderService()
  {
      //重试
      //RetryForever()是一直重试直到成功
      //Retry()是重试最多一次;
      //Retry(n) 是重试最多n次;
      //WaitAndRetry()可以实现“如果出错等待100ms再试还不行再等150ms秒。
       _policy = Policy<string>.Handle()
             .WaitAndRetry(new TimeSpan[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15) });

  }

这里是业务处理失败时,重试3次,分别隔5s,10s,15s。

测试结果:

可以看到,第一次执行因为有异常,然后分别隔5s,10s,15s重试,最后才抛出了异常。

3.4、Polly超时

所谓超时,就是我们指定一段代码的最大运行时间,如果超过这段时间还没有完成,就直接抛出异常。

这里判断超时有两种策略:一个是悲观策略(Pessimistic),一个是乐观策略(Optimistic)。一般我们用悲观策略。需要注意的是,虽然超时抛除了异常,但这段代码的运行并没有停止!

把OrderService构建函数处改成

  public OrderService()
  {
        //超时,业务处理超过3秒就直接返回异常
        _policy = Policy.Timeout<string>(3, Polly.Timeout.TimeoutStrategy.Pessimistic);
  }

把OrderService.cs的CreateOrder()方法让线程睡眠10s

 public string CreateOrder()
 {
      //用polly执行
      return _policy.Execute(() =>
      {
          //业务逻辑 todo
          Console.WriteLine($"{DateTime.Now},开始处理业务");
          Thread.Sleep(10000); //睡眠10s
          Console.WriteLine("处理完成");
          return "成功啦";
      });
  }

执行查看结果:

可以看到,在3s的时候报了polly的超时异常。

3.5、Polly组合策略

上面说的都是单个策略的,其实这些策略是可以组合一起使用的,下面来演示一下。

把OrderService的构造函数修改为:

 public OrderService()
 {
     //重试
     Policy<string> retry = Policy<string>.Handle()
       .WaitAndRetry(new TimeSpan[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15) });

     //降级
     Policy<string> fallback = Policy<string>
          .Handle() //异常故障
          .Fallback(() =>
          {
             //降级回调
             return "降级后的值";
          });
     //Wrap:包裹。policyRetry在里面,policyFallback裹在外面。
     //如果里面出现了故障,则把故障抛出来给外面
     //_policy=Policy.Wrap(policy1, policy2, policy3, policy4, policy5);把更多一起封装。
     _policy = Policy.Wrap(fallback, retry); // fallback.Wrap(retry);
 }

把CreateOrder()修改为

 public string CreateOrder()
 {
     //用polly执行
     return _policy.Execute(() =>
     {
         //业务逻辑 todo
         Console.WriteLine($"{DateTime.Now},开始处理业务");
         throw new Exception("233出错啦");
         Console.WriteLine("处理完成");
         return "成功啦";
     });
 }

测试结果:

重试3次后,返回降级的结果。

上面的Ocelot+Polly的熔断如果去查看Ocelot.Provider.Polly的源码就会发现是超时和熔断的组合实现。

需要注意的是,组合时Policy.Wrap(fallback, retry);里面的顺序也要注意,测试结果是先执行后面的,再执行前面的,即前面的把后面的包在内层,内层执行完再抛出给外层处理。

上面介绍了最常用的策略,Polly也有异步的方法,把上面定义的private Policy_policy; 改成 private AsyncPolicy_policy; 即可。


转自:包子wxl

链接:cnblogs.com/wei325/archive/2021/09/27/15308498.html

浏览 49
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报