目录
常见的限流算法
1、固定窗口
- 规定了我们单位时间处理的请求数量。
- 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。下一秒清零。
- 这种限流算法无法保证限流速率,因而无法保证突然激增的流量。
- 比如说我们限制某个接口 1 分钟只能访问 1000 次,该接口的 QPS 为
- 500,前 55s 这个接口 1 个请求没有接收,后 1s 突然接收了 1000 个请求。在当前场景下,这 1000 个请求在 1s内是没办法被处理的,系统直接就被瞬时的大量请求给击垮了。
2、滑动窗口
- 是固定窗口计数器算法的升级版。
- 它把时间以一定比例分片,每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 60(请求数)/60(窗口数) 的请求,如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
- 很显然, 当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。
3、漏桶
- 我们处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。
- 准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了(和消息队列削峰/限流的思想是一样的)。
4、令牌桶
- 生成令牌:假设有一个装令牌的桶,最多能装 M 个,然后按某个固定的速度(每秒 r 个)往桶中放入令牌,桶满时不再放入
- 消费令牌:我们的每次请求都需要从桶中拿一个令牌才能放行,当桶中没有令牌时即触发限流,这时可以将请求放入一个缓冲队列中排队等待,或者直接拒绝(同样要缓存等待)
限流算法优缺点
- 漏桶和令牌桶算法在处理请求上,一个是以固定速率处理,一个是从桶中获取令牌后才处理。
- 桶大小的设置,正是这个参数可以让令牌桶算法具备处理突发流量的能力。譬如将桶大小设置为 100,生成令牌的速度设置为每秒 10个,那么在系统空闲一段时间的之后(被装满),突然来了 50 个请求,这时系统可以直接按每秒 50个的速度处理,随着桶中的令牌很快用完,处理速度又会慢慢降下来,和生成令牌速度趋于一致。
- 漏桶算法无论来了多少请求,只会一直以固定的速度进行处理。 令牌桶可以提高系统性能,但需要设置合理的桶大小。
- 所以漏桶一般是保护下游,令牌桶一般是保护自己系统。
怎么设定限流预值
1、根据经验先设定一个小的阈值,后续慢慢进行调整。
2、压力测试后总结出来。但这种方式的问题在于压测模型与线上环境不一定一致,接口的单压不能反馈整个系统的状态,全链路压测又难以真实反应实际流量场景流量比例。
3、压测+各应用监控数据。根据系统峰值的QPS与系统资源使用情况,进行等水位放大预估限流阈值,问题在于系统性能拐点未知,单纯的预测不一定准确甚至极大偏离真实场景。
分布式限流方案
1、分布式限流框架
Sentinel、网关限流
2、redis + lua脚本实现
- 实现限流算法,需要反复调用Redis查询与计算,一次限流判断需要多次请求较为耗时。因此我们采用编写Lua脚本运行的方式,将运算过程放在Redis端,使得对Redis进行一次请求就能完成限流的判断。
- 通过对限流两次请求之间的时间和令牌添加速度来计算得出上次请求之后到本次请求时,令牌桶应添加的令牌数量。
- 因此我们在Redis中只需要存储“上次请求的时间”和“令牌桶中的令牌数量”,而桶的大小和令牌的添加速度可以通过参数传入实现动态修改。
- 由于第一次运行脚本时默认令牌桶是满的,因此可以将数据的过期时间设置为令牌桶恢复到满所需的时间,及时释放资源。
参考:
1、限流算法和伪代码:
2、分布式限流