• .Net微服务实践(四)[网关]:Ocelot限流熔断、缓存以及负载均衡


    在上篇.Net微服务实践(三)[网关]:Ocelot配置路由和请求聚合中我们介绍了Ocelot的配置,主要特性路由以及服务聚合。接下来,我们会介绍Ocelot的限流、熔断、缓存以及负载均衡。

    限流

    我们先来看限流的配置

    Reroute节点中的配置如下:

    {
          "DownstreamPathTemplate": "/api/orders",
          "DownstreamScheme": "http",
          "DownstreamHostAndPorts": [
            {
              "Host": "localhost",
              "Port": 5001
            }
          ],
          "UpstreamPathTemplate": "/api/orders",
          "UpstreamHttpMethod": [ "Get" ],
          "RateLimitOptions": {
            "ClientWhitelist": [],
            "EnableRateLimiting": true,
            "Period": "10m",
            "PeriodTimespan": 3,
            "Limit": 1
          }
        }
    

    GlobalConfiguration中的配置如下:

    "GlobalConfiguration": {
        "BaseUrl": "http://localhost:5000",
        //限流
        "RateLimitOptions": {
          "QuotaExceededMessage": "您的请求量超过了配额1/10分钟",
          "HttpStatusCode": 999
        }
    }
    

    配置说明

    在Reroute和GlobalConfiguration节点中添加了RateLimitOptions节点

    • ClientWhitelist - 白名单,也就是不受限流控制的客户端
    • EnableRateLimiting - 是否开启限流
    • Period & Limit - 在一段时间内允许的请求次数
    • PeriodTimespan - 客户端的重试间隔数,也就是客户端间隔多长时间可以重试
    • QuotaExceededMessage - 限流以后的提示信息
    • HttpStatusCode - 超出配额时,返回的http状态码

    示例说明
    客户端在10分钟之内只允许请求一次http://localhost:5000/api/orders,在请求之后3秒钟之后可以重试

    验证

    修改配置,运行示例程序,

    访问http://localhost:5000/api/orders,第一次可以正常获取返回结果,再次访时,显示"您的请求量超过了配额1/10分钟, 并且response状态码是999

    PeriodTimespan的验证

    修改Period为1s, 修改PeriodTimespan为10,这样当前的配置是1秒中允许一个请求,10秒后才能重试。 再次运行示例程序。

    访问http://localhost:5000/api/orders,第一次可以正常获取返回结果, 等待两秒,再次访问,大家想一下,这个时候,会不会返回正常结果(已经过了两秒)这时还是返回999,为什么? 因为尽管配额上是允许的,但是因为配置是客户端10秒以后才能重试,而这时只等待了2秒,所以还是返回999.

    熔断

    Ocelot的熔断使用了Polly来实现,在OcelotGateway项目添加Polly包

    注入Polly

    services
            .AddOcelot()
            .AddPolly();
    

    修改配置

    {
         "DownstreamPathTemplate": "/api/orders",
         "DownstreamScheme": "http",
         "DownstreamHostAndPorts": [
           {
             "Host": "localhost",
             "Port": 5001
           }
         ],
         "UpstreamPathTemplate": "/api/orders",
         "UpstreamHttpMethod": [ "Get" ],
         "QoSOptions": {
           "ExceptionsAllowedBeforeBreaking": 2,
           "DurationOfBreak": 5000,
           "TimeoutValue": 2000
         }
       }
    

    配置说明

    在Reroute节点中添加了QoSOptions节点

    • ExceptionsAllowedBeforeBreaking - 在熔断之前允许的异常次数
    • DurationOfBreak - 熔断时长 ,单位毫秒
    • TimeoutValue - 请求超时设置, 单位毫秒

    示例说明

    当访问http://localhost:5000/api/orders出现2次异常后,服务熔断5秒,如果服务响应超过2秒,也触发熔断条件

    验证

    • 场景一:服务宕机

      修改配置,只启动网关,不启动oder api,访问http://localhost:5000/api/orders,第一次有响应耗时,返回500,第二次也有响应耗时,返回500. 第三次则快速返回503 Service Unavalible, 服务熔断了。

    • 场景二:超时

      修改配置

      修改api/orders代码,等待3秒

    // GET: api/orders
    [Route("api/orders")]
    [HttpGet]
    public  IEnumerable<string> Get()
    {
        Task.Delay(3000).Wait();
        return new string[] { "刘明的订单", "王天的订单" };
    }
    

    启动网关,启动order-api,访问http://localhost:5000/api/orders,返回503

    • 场景三:服务正常响应,但是服务500 内部错误

      修改配置

      修改api/orders代码,抛出异常
    // GET: api/orders
    [Route("api/orders")]
    [HttpGet]
    public  IEnumerable<string> Get()
    {
        throw new Exception("获取所有订单出错");
    }
    

    启动网关,启动order-api,访问http://localhost:5000/api/orders, 不触发熔断

    缓存

    缓存使用了CacheManageer来实现,添加CacheManager包

    Install-Package Ocelot.Cache.CacheManager
    

    注入缓存组件

    services.AddOcelot()
            .AddCacheManager(x =>
            {
                x.WithDictionaryHandle();
            });
    

    Ocelot.json配置文件修改

    {
      "DownstreamPathTemplate": "/api/orders",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/api/orders",
      "UpstreamHttpMethod": [ "Get" ],
      "FileCacheOptions": {
        "TtlSeconds": 60,
        "Region": "orders"
      }
    }
    

    缓存是根据 downstream service 的URL来缓存的
    配置说明

    在Reroute节点中添加了FileCacheOptions节点

    • TtlSeconds - 缓存有效期,单位是秒
    • Region - 缓存分区, 可以通过调用后台Api 来清空一个region下的缓存

    示例说明

    当访问http://localhost:5000/api/orders后,结果会缓存60秒,在缓存有效期内即使原始的order api的返回结果发生变化,通过网关请求时,还是会返回缓存的结果。

    验证

    • 修改配置,只启动网关,启动oder api,访问http://localhost:5000/api/orders,返回的结果如下
    "刘明的订单", "王天的订单"
    
    • 修改api/orders, 重新启动api/orders
    [Route("api/orders")]
    [HttpGet]
    public  IEnumerable<string> Get()
    {
        return new string[] { "帅的订单", "我的订单" };
    }
    
    • 再次访问http://localhost:5000/api/orders, 这个时候是会返回什么结果呢?, 验证显示还是返回
    "刘明的订单", "王天的订单"
    

    因为结果被缓存了

    • 等待2分钟,再访问http://localhost:5000/api/orders,这个时候是会返回什么结果呢?, 验证显示返回了新的结果
    "帅的订单", "我的订单"
    

    因为缓存有效期已经过了

    Header转化

    Ocelot允许在上游服务的request和下游服务的response的header中添加、替换信息

    配置如下:

    {
      "DownstreamPathTemplate": "/api/shopping-carts",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "DownstreamHeaderTransform": {
        "devops": "rdc"
      },
      "UpstreamPathTemplate": "/api/shopping-carts",
      "UpstreamHttpMethod": [ "Get" ],
      "UpstreamHeaderTransform": {
        "lakin": "rdc",
        "CI": "msbuild, jenkins",
        "Location": "http://localhost:5001, {BaseUrl}"
      }
    
    }
    

    配置说明

    在Reroute节点中添加了DownstreamHeaderTransform节点和UpstreamHeaderTransform节点

    "DownstreamHeaderTransform": {
        "devops": "rdc"
      }
    

    说明:在下游服务的response中添加一个header, key是devops, value是rdc

    "UpstreamHeaderTransform": {
        "lakin": "rdc",
        "CI": "msbuild, jenkins",
        "Location": "http://localhost:5001, {BaseUrl}"
      }
    
    • 添加header信息

      在上游服务的request中添加一个header, key是lakin, value是rdc
    • 替换header信息

      在上游服务的request中, 将key是CI的header, 其值由msbuild替换为jenkins
    • 替换时使用placeholder

      在上游服务的request中, 将key是Location的header, 其值由http://localhost:5001替换为{BaseUrl} placehokder

    示例说明

    当访问http://localhost:5000/api/orders后,结果会缓存60秒,在缓存有效期内即使原始的order api的返回结果发生变化,通过网关请求时,还是会返回缓存的结果。

    验证

    • 修改Order Service, 添加一个ShoppingCart api, 代码如下
    // GET: api/shopping-carts
    [Route("api/shopping-carts")]
    [HttpGet]
    public IEnumerable<string> Get()
    {
        Console.WriteLine($"开始打印header信息");
        foreach (var item in this.Request.Headers)
        {
            Console.WriteLine($"{item.Key} - {item.Value}");
        }
        Console.WriteLine($"打印header信息完成");
        return new string[] { "洗发水", "无人机" };
    }
    
    • 启动网关、启动Order Service
    • 使用Postman调用http://localhost:5000/api/shopping-carts, 在request中添加两个header项
        "CI": "msbuild",
        "Location": "http://localhost:5001"
    
    • 发起请求
    • 在Order Service的控制台,可以看到如下输出, 添加一个header项,替换两个header项的值
    开始打印header信息CI
    lakin - rdc
    CI - jenkins
    Location - http://localhost:5000
    打印header信息完成
    
    • 检查Postman中response的header信息,会发现添加了如下的header项
    devops - rdc
    

    HTTP方法转换

    Ocelot允许在路由时转化HTTP方法

    {
      "DownstreamPathTemplate": "/api/shopping-carts",
      "DownstreamScheme": "http",
      "DownstreamHttpMethod": "POST",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/api/shopping-carts",
      "UpstreamHttpMethod": [ "Get" ]
    }
    

    示例说明
    上述示例中,将GET /api/shopping-carts 路由到 POST /api/shopping-carts, 将GET转换成了POST

    适用场景:例如有些已经存在的的API,因为某些历史原因都是用POST,在通过网关对外提供服务时,就可以按照标准API进行转换

    验证

    • 当前GET /api/shopping-carts返回如下结果
    "洗发水", "无人机"
    
    • 我们在ShoppingCartController中添加一个新的POST API
    [Route("api/shopping-carts")]
    [HttpPost]
    public string Post()
    {
        return "添加商品到购物车成功";
    }
    
    添加商品到购物车成功
    

    负载均衡

    Ocelot内置了负载均衡,我们先来看配置

    {
         "DownstreamPathTemplate": "/api/orders",
         "DownstreamScheme": "http",
         "DownstreamHostAndPorts": [
           {
             "Host": "localhost",
             "Port": 5001
           },
           {
             "Host": "localhost",
             "Port": 6001
           }
         ],
         "UpstreamPathTemplate": "/api/orders",
         "UpstreamHttpMethod": [ "Get" ],
         "LoadBalancerOptions": {
           "Type": "RoundRobin"
         }
    }
    

    配置说明

    在DownstreamHostAndPorts指指定多个服务地址

    在Reroute节点中添加LoadBalancerOptions,这是负载均衡的配置节点,其中Type属性指定了负载均衡的算法, 它有如下几个值:

    • LeastConnection – 将请求发往最空闲的那个服务器
    • RoundRobin – 轮流发送
    • NoLoadBalance – 总是发往第一个请求(如果配置了服务发现,则总是发往发现的第一个服务地址)

    验证

    • 启动网关
    • 修改api/orders的代码,并启动Order Service
    [Route("api/orders")]
    [HttpGet]
    public  IEnumerable<string> Get()
    {
        return new string[] { "刘明的订单", "王天的订单" };
    }
    
    • 拷贝一份Order Service的副本,修改api/orders的代码,修改副本的启动端口为6001, 并启动Order Service
    [Route("api/orders")]
    [HttpGet]
    public  IEnumerable<string> Get()
    {
        return new string[] { "帅的订单", "我的订单" };
    }
    

    第一次的结果是

    "刘明的订单", "王天的订单"
    

    第二次的结果是

    "帅的订单", "我的订单"
    

    第三次的结果是

    "刘明的订单", "王天的订单"
    

    注入/重写中间件

    Ocelot本身是一组中间件,它也提供了方式来注入和重写其中的某些中间件:

    • PreErrorResponderMiddleware - 在所有Ocelot中间件之前运行, 允许用户在Ocelot管道运行之前和运行之后提供任何行为。
    • PreAuthenticationMiddleware - 提供预身份认证逻辑,在身份认证中间件之前运行
    • AuthenticationMiddleware - 覆写Ocelot中间件提供的身份认证逻辑
    • PreAuthorisationMiddleware - 提供预授权逻辑,在授权中间件之前运行
    • AuthorisationMiddleware - 覆写Ocelot中间件提供的授权逻辑
    • PreQueryStringBuilderMiddleware - 在QueryString转换之前,提供预处理逻辑

    下面是注入PreErrorResponderMiddleware中间件的代码示例:

    //注入中间件
    var configuration = new OcelotPipelineConfiguration
    {
        PreErrorResponderMiddleware = async (ctx, next) =>
        {
            ctx.HttpContext.Request.Headers.Add("myreq", "ocelot-request");
            await next.Invoke();
        }
    };
    app.UseOcelot(configuration).Wait();
    

    注意: Ocelot也是一组中间件,所以可以在Ocelot中间件之前,按常规方式添加任何中间件, 但是不能在Ocelot中间件之后添加,因为Ocelot没有调用 next

    后台管理

    Ocelot提供了一组后台管理的API, 从前三篇文章可以看出,Ocelot主要也就是配置文件的管理,所以API主要也就是管理配置

    • 获取管理后台的token

      POST {adminPath}/connect/token
    • 获取配置

      GET {adminPath}/configuration
    • 创建/修改配置

      POST {adminPath}/configuration
    • 删除缓存

      DELETE {adminPath}/outputcache/{region}

    最后

    本篇我们介绍了Ocelot的限流、熔断、缓存、负载均衡以及其他一些特性。到目前为止,Ocelot的基本配置和功能都已经介绍完了。接下里我们会结合consul来介绍服务发现,以及Ocelot和Consul的集成.Net微服务实践(五)[服务发现]:Consul介绍和环境搭建

    示例代码下载地址: https://github.com/lcyhjx/ocelot-demo/tree/master

  • 相关阅读:
    Linux安装Jenkins
    python实现寻找目录中最新的文件
    python代码实现邮件发送
    面向对象(一) 类与类之间的关系之依赖和关联
    常见模块(五) random模块
    装饰器如何装饰带参数的函数
    常见模块(四) os模块
    常见模块(二) logging模块
    常见模块(三) pickle模块和json模块
    初识模块
  • 原文地址:https://www.cnblogs.com/lcyhjx/p/12687152.html
Copyright © 2020-2023  润新知