文章目录
目的
在流量高峰期或因为活动、热点事件、恶意攻击等原因导致流量突增时,通过对请求流量速率进行限制,保证请求流量在合理范围内,避免因为超出预期的大流量导致服务整体性能下降、响应缓慢或不可用。在触发限流后,可以通过拒绝部分服务、等待、排队、降级等策略保护业务系统。
常用限流算法
固定窗口算法
- 定义:限制固定时间段内,特定api的最大请求量,如限制每秒最多请求100次,超出100次触发限流策略。
- 优点:实现简单直观
- 缺点:
- 请求分布不均,不能平滑流量,容易出现部分时刻“服务不可用”的表现。如1秒内,前10ms请求100次,导致后990ms全部请求被拒绝。即请求分布不均,部分请求在某个时刻聚集,导致这个时刻所属的时间窗口内的其他时刻被拒绝。
- 出现流量尖峰,容易导致单位时间超出服务器预期负载,如预期单位时间1s内请求100次,上一秒的最后10ms请求100次,下一秒的最早10ms请求100次,则在单位时间1秒内,出现200次请求,超出服务承载预期。
滑动窗口算法
定义
将一个时间区间划分为N个长度固定的小时间窗口,每一次请求考察当前时间往前N个连续时间窗口的请求量总和,如果超过阈值则触发限流策略。如配置请求阈值100,将1s时间划分为5个连续的时间窗口,每个窗口长度250ms,则意味这上一秒当前250ms加上一秒的最后750ms请求量不会100个请求。
优点
解决了固定窗口流量尖峰的问题,确保在任意时刻,过去窗口时间内的请求不会超出阈值。
缺点
- 不能解决请求分布不均的问题,即无法平滑流量
- 实现更复杂,需要维护时间窗口,占用内存更多,计算时间复杂度也相应变大。
令牌桶算法
定义
存在一个令牌桶计数器,每隔一定时间放入一块令牌,每次请求取出一块令牌。,令牌桶有最大容量限制,当计数器令牌数达到最大值,则不再放入,如果计数器令牌数为0,请求则触发限流策略。如每隔10ms放入一块令牌,最大令牌数为100,假设某个时刻令牌桶放满令牌,则这个时刻的前10ms能请求100次,清20ms能请求101次。
优点
- 解决了固定窗口流量尖峰的问题,确保在任意时刻,过去窗口时间内的请求不会超出阈值。
- 可以有效平滑流量,因为令牌桶的令牌是匀速放入的
- 相对滑动窗口更节省内存
缺点
实现复杂,时间复杂度高
注意事项
- 令牌桶预热
漏桶算法
定义
相对于令牌桶是固定速率放入令牌,在没有令牌的时候拒绝请求,漏桶则是固定数据漏出请求,当请求量过大,流入请求超过桶容量,则触发限流策略。具体实现时,可以将漏桶想象成一个有流量限制的先进先出的队列,在队列不为空情况下,每隔一定时间取出一个队列请求进行处理相应,如果队列已经满了,再来请求则触发限流相关策略。如限制每10ms漏出一个请求,桶容量为100个请求,则每10ms最多能处理一个请求。
优点
实现相对简单,可以限制服务请求速率,并且稳定在一个常速。
缺点
对于特发流量处理效率过低,在没有到达服务器负载阈值,也只能串行处理请求。
分布式限流
可以基于Redis实现分布式限流。
基于固定窗口的分布式限流实现
我们可以基于Redis来实现固定窗口的分布式限流算法:
可以为每个请求路径配置一个有超时时间的计数器,每次请求给计数器加一,如果超过限制阈值,则返回限流标识,具体实现如下:
// 限流桶
private static final String BUCKET = "BUCKET";
/**
* 基于redis的固定窗口分布式限流
* @param point 限流方法标识
* @param limit 限流阈值
* @param timeout 固定时间窗口大小
* @return 如果触发限流,返回-1,否则返回当前请求在固定窗口里的请求计数
*/
public long acquireTokenFromBucket(String point, int limit, int timeout) {
Jedis jedis = jedisPool.getResource();
try {
// 标识当前请求
Long counter = jedis.incr(BUCKET + point