Tyk 是一款出色的开源网关,它是由 Go 编写的,核心组件包括了 API Gateway、Tyk Dashboard、 Tyk Pumpd 和 Tyk Identity BrokerTyk 等,我们主要是分析下 API Gateway 这个组件的限流设计。
0x1 流程分析
0x2 关键代码
从流程得知,关键代码是在 ForwardMessage 这个代码块中,分析得知,Tyk 的限流方式主要有三种
limitSentinel 方式
其中 limitSentinel、limitRedis 都是采用滑动窗口的限流策略,Tyk 滑动窗口逻辑的封装为 doRollingWindowWrite。两种的区别就是 limitSentinel 是异步监察的方式,故名曰 “哨兵”(就是暗中观察,不知不觉间咔嚓敌人),通过 go doRollingWindowWrite,将 RL 的检查结果写入存储(memory、redis 等),再通过 store.GetRawKey(rateLimiterSentinelKey) 获取监察结果,判断是否放行流量。
判断逻辑在 doRollingWindowWrite 函数
limitRedis 方式
limitRedis 限流判断逻辑跟limitSentinel一样,唯一不同的是放行方式是串行的滑动窗口策略,直接执行 doRollingWindowWrite 获取限流的检查结果,判断是否放行流量。
分布式限流
分布式限流是指多个GW节点(分布式部署,负载均衡策略)共享流量限制,Tyk的处理分两种请况,一是当 GW 服务节点只有一 1 台时或者 服务节点 * 服务限流阀值 < 配置 Rate(允许多少流量)/ 配置 Per(间隔多长时间) ,直接采用 limitDRL 来判断限流条件,limitDRL 是漏桶算法策略。
二是其他情况下,采用 limitRedis(各个节点公用redis来共享限流实时数据) 来判断限流条件,limitDRL 是漏桶算法策略。
0x3 限流方式总结
从代码逻辑上看
limitSentinel 方式性能应该较好,因为是异步判断 flag 的方式去返回限流检查,也也有缺点是并发比较大时可能要放过溢出的流量,数据的储存方式可以是 redis,也可以是本地 memory。
limitRedis 最稳,串行中间人检查的方式,不会放过任何溢出的流量,缺点是性能会比 limitSentinel 差点,看场景的取舍,这种方式必须要有 redis,因为分布式限流有一个分支条件也是采用这种方式,需要 redis 做数据共享、原子操作等。
在不指定限流方式 EnableSentinelRateLimiter、EnableRedisRollingLimiter 的情况下,当 GW 服务节点只有一个或多个节点分布式限流阀值低于 rate/per 时,Tyk 采专用最直接的限流方式,漏桶算法,这么看起来 Tyk 认为如果只有自己一个节点实行限流策略,并且流量阀值,此种方式最佳,这个就见仁见智了。
0x4 为什么这样设计?
我们都知道滑动窗口的方式并没有真正解决临界突发流量问题,可能 Tyk 本身的意图是为了稳定 GW 的流量而这样设计的(我认为你不应该承担这么大流量,你就不应该支持这么大流量),从个根本上拒绝突发大流量,保证了 GW 下面的服务稳定性,可用于一些稳定性比较高的场景,例如抢购、秒杀、热点资讯等。
那为什么在不指定限流方式为
SentinelRateLimiter、RedisRollingLimiter 的情况下, GW 服务节点只有一个或多个节点分布式限流阀值低于 rate/per 时,又要设计成漏桶策略呢?我的理解是,这是 Tyk 提供给用户的一个激进点的可用于处理突发流量的一种方式,漏桶策略请求速率不固定,可大可小,但是流出速率固定,一方面保证的 GW 去请求服务资源不被打满卡死,另一方面可尝试尽可能快的去处理突发流量请求(多个节点分布式限流阀值低于 rate/per 这个配置时理论上应该 GW 处理请求更快,因为每次处理得不多),但是漏桶算法一旦超出桶的容量,会大量返回给 FW 请求失败,这个要用户衡量自己场景而定了。
另外就是并没有发现 Tyk 用最激进的 token 算法策略,可见 Tyk 对于 GW 的设计还是稳定为主,并且不能到后面的服务产生坏影响,很安逸。