注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
本系列为 Go 进阶训练营 笔记,访问 博客: Go进阶训练营, 即可查看当前更新进度,部分文章篇幅较长,使用 PC 大屏浏览体验更佳。
在前面限流的三篇文章,我们学习了令牌桶、漏桶算法的原理、实现以及使用方式,不知道你有没有觉得这两种算法存在着一些问题。
这两种算法最大的一个问题就是他们都属于需要提前设置阈值的算法,基于 QPS 进行限流的时候最麻烦的就是这个阈值应该怎么设定。一般来说我们可以通过压测来决定这个阈值。
既然这种方式有这么多的缺点,那有没有办法解决呢?答案就是今天讲到的 自适应限流
前面我们遇到的主要问题就是每个服务实例的限流阈值实际应该是动态变化的,我们应该根据系统能够承载的最大吞吐量,来进行限流,当当前的流量大于最大吞吐的时候就限制流量进入,反之则允许通过。那现在的问题就是
利特尔法则由麻省理工大学斯隆商学院(MIT Sloan School of Management)的教授 John Little﹐于 1961 年所提出与证明。它是一个有关提前期与在制品关系的简单数学公式,这一法则为精益生产的改善方向指明了道路。 —- MBA 智库百科 (mbalib.com)

如上图所示,如果我们开一个小店,平均每分钟进店 2 个客人(λ),每位客人从等待到完成交易需要 4 分钟(W),那我们店里能承载的客人数量就是 2 * 4 = 8 个人
同理,我们可以将 λ 当做 QPS, W 呢是每个请求需要花费的时间,那我们的系统的吞吐就是 L = λ * W ,所以我们可以使用利特尔法则来计算系统的吞吐量。
首先我们可以通过统计过去一段时间的数据,获取到平均每秒的请求量,也就是 QPS,以及请求的耗时时间,为了避免出现前面 900ms 一个请求都没有最后 100ms 请求特别多的情况,我们可以使用滑动窗口算法来进行统计。
最容易想到的就是我们从系统启动开始,就把这些值给保存下来,然后计算一个吞吐的最大值,用这个来表示我们的最大吞吐量就可以了。但是这样存在一个问题是,我们很多系统其实都不是独占一台机器的,一个物理机上面往往有很多服务,并且一般还存在一些超卖,所以可能第一个小时最大处理能力是 100,但是这台节点上其他服务实例同时都在抢占资源的时候,这个处理能力最多就只能到 80 了
所以我们需要一个数据来做启发阈值,只要这个指标达到了阈值那我们就进入流控当中。常见的选择一般是 CPU、Memory、System Load,这里我们以 CPU 为例
只要我们的 CPU 负载超过 80% 的时候,获取过去 5s 的最大吞吐数据,然后再统计当前系统中的请求数量,只要当前系统中的请求数大于最大吞吐那么我们就丢弃这个请求。
1 | |
cpu > 800 表示 CPU 负载大于 80% 进入限流(Now - PrevDrop) < 1s 这个表示只要触发过 1 次限流,那么 1s 内都会去做限流的判定,这是为了避免反复出现限流恢复导致请求时间和系统负载产生大量毛刺(MaxPass * MinRt * windows / 1000) < InFlight 判断当前负载是否大于最大负载InFlight 表示当前系统中有多少请求(MaxPass * MinRt * windows / 1000) 表示过去一段时间的最大负载MaxPass 表示最近 5s 内,单个采样窗口中最大的请求数MinRt 表示最近 5s 内,单个采样窗口中最小的响应时间windows 表示一秒内采样窗口的数量,默认配置中是 5s 50 个采样,那么 windows 的值为 10。1 | |
1 | |
这个方法主要是给中间件使用的
shouldDrop 方法判断这个请求是否应该丢弃function 用于请求结束之后Inflight -11 | |
这个方法其实就是开头讲到的限流公式了,逻辑如下图所示
prevDrop 清零然后返回 falseprevDrop,返回 true 需要丢弃请求1 | |
这个就是计算过去一段时间系统的最大负载是多少
这篇文章我们讲了一下为什么需要自适应限流,令牌桶和漏桶这类需要手动设置 rps 算法的问题所在,了解了自适应限流的实现原理,最后看了一下 kratos 当中是如何实现自适应限流的。但是由于篇幅关系,CPU 的数据如何进行统计,文章中提到了很多次的滑动窗口是个什么原理这些知识点大家可以自行查看 kratos 中的源码,或者去看极客时间的 Go 进阶训练营都有讲到。
kratos 中的限流算法其实灵感源于 Google SRE,实现上参考了 sentinel,其中一个有意思的点是 sentinel 默认使用 load 作为启发阈值,而 kratos 使用了 cpu,kratos 为什么要使用 cpu 呢?这个大家可以自己想想(答案可以自行观看极客时间的 Go 进阶训练营)
而 sentinel 的实现其实是参考了 TCP 中的 BBR 算法,在 BBR 的基础上加上了 load 作为启发阈值的判断,所以多了解一下基础知识总是没错的,指不定当下遇到的场景就能解决。
注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
本系列为 Go 进阶训练营 笔记,访问 博客: Go进阶训练营, 即可查看当前更新进度,部分文章篇幅较长,使用 PC 大屏浏览体验更佳。
在前面限流的三篇文章,我们学习了令牌桶、漏桶算法的原理、实现以及使用方式,不知道你有没有觉得这两种算法存在着一些问题。
这两种算法最大的一个问题就是他们都属于需要提前设置阈值的算法,基于 QPS 进行限流的时候最麻烦的就是这个阈值应该怎么设定。一般来说我们可以通过压测来决定这个阈值。
既然这种方式有这么多的缺点,那有没有办法解决呢?答案就是今天讲到的 自适应限流
前面我们遇到的主要问题就是每个服务实例的限流阈值实际应该是动态变化的,我们应该根据系统能够承载的最大吞吐量,来进行限流,当当前的流量大于最大吞吐的时候就限制流量进入,反之则允许通过。那现在的问题就是
利特尔法则由麻省理工大学斯隆商学院(MIT Sloan School of Management)的教授 John Little﹐于 1961 年所提出与证明。它是一个有关提前期与在制品关系的简单数学公式,这一法则为精益生产的改善方向指明了道路。 —- MBA 智库百科 (mbalib.com)

如上图所示,如果我们开一个小店,平均每分钟进店 2 个客人(λ),每位客人从等待到完成交易需要 4 分钟(W),那我们店里能承载的客人数量就是 2 * 4 = 8 个人
同理,我们可以将 λ 当做 QPS, W 呢是每个请求需要花费的时间,那我们的系统的吞吐就是 L = λ * W ,所以我们可以使用利特尔法则来计算系统的吞吐量。
首先我们可以通过统计过去一段时间的数据,获取到平均每秒的请求量,也就是 QPS,以及请求的耗时时间,为了避免出现前面 900ms 一个请求都没有最后 100ms 请求特别多的情况,我们可以使用滑动窗口算法来进行统计。
最容易想到的就是我们从系统启动开始,就把这些值给保存下来,然后计算一个吞吐的最大值,用这个来表示我们的最大吞吐量就可以了。但是这样存在一个问题是,我们很多系统其实都不是独占一台机器的,一个物理机上面往往有很多服务,并且一般还存在一些超卖,所以可能第一个小时最大处理能力是 100,但是这台节点上其他服务实例同时都在抢占资源的时候,这个处理能力最多就只能到 80 了
所以我们需要一个数据来做启发阈值,只要这个指标达到了阈值那我们就进入流控当中。常见的选择一般是 CPU、Memory、System Load,这里我们以 CPU 为例
只要我们的 CPU 负载超过 80% 的时候,获取过去 5s 的最大吞吐数据,然后再统计当前系统中的请求数量,只要当前系统中的请求数大于最大吞吐那么我们就丢弃这个请求。
1 | |
cpu > 800 表示 CPU 负载大于 80% 进入限流(Now - PrevDrop) < 1s 这个表示只要触发过 1 次限流,那么 1s 内都会去做限流的判定,这是为了避免反复出现限流恢复导致请求时间和系统负载产生大量毛刺(MaxPass * MinRt * windows / 1000) < InFlight 判断当前负载是否大于最大负载InFlight 表示当前系统中有多少请求(MaxPass * MinRt * windows / 1000) 表示过去一段时间的最大负载MaxPass 表示最近 5s 内,单个采样窗口中最大的请求数MinRt 表示最近 5s 内,单个采样窗口中最小的响应时间windows 表示一秒内采样窗口的数量,默认配置中是 5s 50 个采样,那么 windows 的值为 10。1 | |
1 | |
这个方法主要是给中间件使用的
shouldDrop 方法判断这个请求是否应该丢弃function 用于请求结束之后Inflight -11 | |
这个方法其实就是开头讲到的限流公式了,逻辑如下图所示
prevDrop 清零然后返回 falseprevDrop,返回 true 需要丢弃请求1 | |
这个就是计算过去一段时间系统的最大负载是多少
这篇文章我们讲了一下为什么需要自适应限流,令牌桶和漏桶这类需要手动设置 rps 算法的问题所在,了解了自适应限流的实现原理,最后看了一下 kratos 当中是如何实现自适应限流的。但是由于篇幅关系,CPU 的数据如何进行统计,文章中提到了很多次的滑动窗口是个什么原理这些知识点大家可以自行查看 kratos 中的源码,或者去看极客时间的 Go 进阶训练营都有讲到。
kratos 中的限流算法其实灵感源于 Google SRE,实现上参考了 sentinel,其中一个有意思的点是 sentinel 默认使用 load 作为启发阈值,而 kratos 使用了 cpu,kratos 为什么要使用 cpu 呢?这个大家可以自己想想(答案可以自行观看极客时间的 Go 进阶训练营)
而 sentinel 的实现其实是参考了 TCP 中的 BBR 算法,在 BBR 的基础上加上了 load 作为启发阈值的判断,所以多了解一下基础知识总是没错的,指不定当下遇到的场景就能解决。