网关

在分布式系统中,一般需要一个软件来负责对入站流量在 7 层进行统一的管理,它主要负责请求的路由、限流、负载均衡、安全认证等功能。
在大团队中, nginx 、 kong 、 traefik 等都是比较常见的网关软件。虽然这些软件有比较丰富的开源社区组件支持,但进行二次开发需要同时掌握多项技术和编程语言,二次开发门槛和成本较高。
因此我们选择了使用 dotnet 开发的 Ocelot 作为我们的网关软件。 Ocelot 官方文档

ocelot

Consul 存储

为了方便我们在运行中修改网关的路由转发配置,我们推荐使用 Consul 作为配置存储。

// 程序启动的 ocelot.json 配置
{
  "GlobalConfiguration": {
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "ConfigurationKey": "demo.gateway.ocelot" // !
    }
  }
}
// 设置使用 consul 作为配置存储
services.AddOcelot()
        .AddConsul()
        .AddConfigStoredInConsul(); // !

ocelot-store-consul

通过此配置,我们在 Consul 中调整路由配置后, ocelot 程序会自动生效并应用。

小技巧

需要注意的是 ocelot 中有部份缓存是根据 UpstreamPathTemplate 来缓存的,所以在修改路由配置时,需要注意 UpstreamPathTemplate 的变化。
当不改变 UpstreamPathTemplate 时,有些改动不会生效。

限流

常见的限流算法

ocelot 支持在每个路由配置中增加服务的限流策略,以帮助下游服务避免因恶意的 ddos 攻击等原因导致的服务不可用。

原理

ocelot 使用 clientId + upstreamPath 组成分布式缓存的 key ,然后根据配置的统计时间段开始为请求计数。 每次请求都会被计数,每次请求都会去判断统计的合计数量是否超过限制,当超过限制后,后续请求将被直接返回而不再转发到下游服务。

counter

提示

ocelot 支持两种计数器缓存形式:

  1. 内存缓存

  2. 分布式缓存

这两种计数器统计方式,在网关负载均衡时,内存计数器每个应用独立计数,而使用分布式缓存的计数器,所有的网关实例都被一起计数。

增加限流

  1. 在全局配置中,增加发生限流时的 http 响应码和 Header 可以标识客户端的 key

    {
      "GlobalConfiguration": {
        "RateLimitOptions": {
          "DisableRateLimitHeaders": false,
          "QuotaExceededMessage": "Too Many Requests",
          "HttpStatusCode": 429,
          "ClientIdHeader": "ClientId"
        }
      }
    }
    

    注意

    当不指定 ClientIdHeader 时,对于网关来说,每次的请求就不能标记 client 来源, 此时,所有的请求网关将会认为是同一个 client 发起的。

  2. 在每个服务的路由处增加限流规则。

    {
      "Routes": [
        {
          "DownstreamPathTemplate": "/{url}",
          "DownstreamScheme": "http",
          "UpstreamPathTemplate": "/order/{url}",
          "UpstreamHttpMethod": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
          "ServiceName": "order",
          "RateLimitOptions": {
            "ClientWhitelist": [],
            "EnableRateLimiting": true,
            "Period": "1s",
            "PeriodTimespan": 5,
            "Limit": 20
          }
        }
      ]
    }
    

    名称

    描述

    EnableRateLimiting

    是否启用限流

    ClientWhitelist

    不受限流控制的 ip 白名单

    Period

    统计流量数量的时间范围,如:1s, 5m, 1h,1d

    PeriodTimespan

    发生限流后,客户端再次发起需要等待的时常

    Limit

    客户端在统计期内可以发起的最大请求数量

    注意

    ocelot 自身实现的限流功能,仅通过简单的计数器来限制每个客户端的请求次数,不能控制客户端的请求速率。
    目前 ASP.NET Core 7 中已经内置了限流的功能,限流功能因使用微软提供的官方解决方案。

  3. Startup 增加分布式缓存的实现

    // 根据不同版本的 ocelot,需要增加不同的限流实现
    services.AddOcelot()
            .AddRateLimiting()
            .AddDistributedRateLimiting();
    

    注意

    ocelot 的限流功能依赖于 IDistributedCache 接口,所以需要在 Startup 中注入 IDistributedCache 的实现。

熔断 QOS

连接池

ocelot 本身并没有连接相关的池化设计。它依赖 dotnet core 本身的 HttpClient 的线程池化模型来处理请求。

httpclient-pool

如上图所示, HttpClient 下层依赖的 SocketsHttpHandler 会为每一个 endpoint 创建队列和连接池。
线程会在多个 endpoint 之间调度,当某个 endpoint 请求过多时,只会影响到积压的 endpoint ,其他的转发并不受影响。

基于 K8S 的灰度发布

本身,K8S 的 Deployment 就是通过滚动更新来实现新版本的发布。 但是,有时我们出于业务角度的考虑,需要做 A/B Test基于用户的灰度基于流量比例的灰度 等工作,我们就需要结合 K8S 的发布和负载规则来完成相关的发布动作。

首先,不管怎样的情况我们都需要在集群中,通过不同 Deployment 来部署不同版本的应用。

alt

蓝绿发布

蓝绿发布的策略比较简单,仅需要给不同的 Pod 增加上 version 标签。在 Service 中,通过 labelSelector 来选择不同版本的 Pod。

alt

灰度发布

灰度发布的策略比较复杂,需要根据不同的需求来确定实现方式。

  1. 基于 K8S Service 的灰度发布

    通过 K8S Service 和 Pod 数量,我们可以做到基于流量比例的灰度发布。如下图,新版本的 V2 就获得了 25% 的流量。

    alt

  2. 基于 K8S Ingress 的灰度发布

    一般的 ingress 都支持请求流量比例的负载。通过配置下游服务的权重进行比较精准的流量切分。

    alt

  3. 用户相关的灰度发布

    有时候,某些业务功能我们希望根据用户的特征来进行灰度发布。
    一般的做法是,通过 sso 服务来标记特征用户,再通过 ingress 的 router 规则,将请求转发到不同的服务上。
    如下图,我们可以通过用户的特征,将请求转发到不同的服务上。

    alt

小技巧

如果是在非 K8S 环境中,我们就需要通过 Ocelot 网关来实现灰度发布。参考 Ocelot 基于 Http Header 配置路由的文档 传送门