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

Consul 存储
为了方便我们在运行中修改网关的路由转发配置,我们推荐使用 Consul 作为配置存储。
// 程序启动的 ocelot.json 配置
{
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "localhost",
"Port": 8500,
"ConfigurationKey": "demo.gateway.ocelot" // !
}
}
}
// 设置使用 consul 作为配置存储
services.AddOcelot()
.AddConsul()
.AddConfigStoredInConsul(); // !

通过此配置,我们在 Consul 中调整路由配置后, ocelot 程序会自动生效并应用。
小技巧
需要注意的是 ocelot 中有部份缓存是根据 UpstreamPathTemplate 来缓存的,所以在修改路由配置时,需要注意 UpstreamPathTemplate 的变化。
当不改变 UpstreamPathTemplate 时,有些改动不会生效。
限流
常见的限流算法

ocelot 支持在每个路由配置中增加服务的限流策略,以帮助下游服务避免因恶意的 ddos 攻击等原因导致的服务不可用。
原理
ocelot 使用 clientId + upstreamPath 组成分布式缓存的 key ,然后根据配置的统计时间段开始为请求计数。
每次请求都会被计数,每次请求都会去判断统计的合计数量是否超过限制,当超过限制后,后续请求将被直接返回而不再转发到下游服务。

提示
ocelot 支持两种计数器缓存形式:
内存缓存
分布式缓存
这两种计数器统计方式,在网关负载均衡时,内存计数器每个应用独立计数,而使用分布式缓存的计数器,所有的网关实例都被一起计数。
增加限流
在全局配置中,增加发生限流时的 http 响应码和 Header 可以标识客户端的 key
{ "GlobalConfiguration": { "RateLimitOptions": { "DisableRateLimitHeaders": false, "QuotaExceededMessage": "Too Many Requests", "HttpStatusCode": 429, "ClientIdHeader": "ClientId" } } }
注意
当不指定
ClientIdHeader时,对于网关来说,每次的请求就不能标记 client 来源, 此时,所有的请求网关将会认为是同一个 client 发起的。在每个服务的路由处增加限流规则。
{ "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,1dPeriodTimespan
发生限流后,客户端再次发起需要等待的时常
Limit
客户端在统计期内可以发起的最大请求数量
注意
ocelot 自身实现的限流功能,仅通过简单的计数器来限制每个客户端的请求次数,不能控制客户端的请求速率。
目前 ASP.NET Core 7 中已经内置了限流的功能,限流功能因使用微软提供的官方解决方案。Startup 增加分布式缓存的实现
// 根据不同版本的 ocelot,需要增加不同的限流实现 services.AddOcelot() .AddRateLimiting() .AddDistributedRateLimiting();
注意
ocelot 的限流功能依赖于
IDistributedCache接口,所以需要在Startup中注入IDistributedCache的实现。
熔断 QOS
连接池
ocelot 本身并没有连接相关的池化设计。它依赖 dotnet core 本身的 HttpClient 的线程池化模型来处理请求。

如上图所示, HttpClient 下层依赖的 SocketsHttpHandler 会为每一个 endpoint 创建队列和连接池。
线程会在多个 endpoint 之间调度,当某个 endpoint 请求过多时,只会影响到积压的 endpoint ,其他的转发并不受影响。
基于 K8S 的灰度发布
本身,K8S 的 Deployment 就是通过滚动更新来实现新版本的发布。
但是,有时我们出于业务角度的考虑,需要做 A/B Test 、 基于用户的灰度 、 基于流量比例的灰度 等工作,我们就需要结合 K8S 的发布和负载规则来完成相关的发布动作。
首先,不管怎样的情况我们都需要在集群中,通过不同 Deployment 来部署不同版本的应用。

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

灰度发布
灰度发布的策略比较复杂,需要根据不同的需求来确定实现方式。
基于 K8S Service 的灰度发布
通过 K8S Service 和 Pod 数量,我们可以做到基于流量比例的灰度发布。如下图,新版本的 V2 就获得了 25% 的流量。

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

用户相关的灰度发布
有时候,某些业务功能我们希望根据用户的特征来进行灰度发布。
一般的做法是,通过 sso 服务来标记特征用户,再通过 ingress 的 router 规则,将请求转发到不同的服务上。
如下图,我们可以通过用户的特征,将请求转发到不同的服务上。
小技巧
如果是在非 K8S 环境中,我们就需要通过 Ocelot 网关来实现灰度发布。参考 Ocelot 基于 Http Header 配置路由的文档 传送门