可作为面试时的亮点写在简历上
计数器固定窗口算法实现简单,容易理解。和漏斗算法相比,新来的请求也能够被马上处理到。但是流量曲线可能不够平滑,有“突刺现象”,在窗口切换时可能会产生两倍于阈值流量的请求;
计数器滑动窗口算法作为计数器固定窗口算法的一种改进,有效解决了窗口切换时可能会产生两倍于阈值流量请求的问题,但计算机很难处理滑动的行为,只能通过轮询的方式模拟(redis的过期时间本质上也是轮询),会占用CPU资源/内存浪费;
漏斗算法能够对流量起到整流的作用,让随机不稳定的流量以固定的速率流出,但是不能解决流量突发的问题;
令牌桶算法作为漏斗算法的一种改进,除了能够起到平滑流量的作用,还允许一定程度的流量突发。但可能需要初始化;
以上四种限流算法都有自身的特点,具体使用时还是要结合自身的场景进行选取。
令牌桶算法一般用于保护自身的系统,对调用者进行限流,保护自身的系统不被突发的流量打垮。如果自身的系统实际的处理能力强于配置的流量限制时,可以允许一定程度的流量突发,使得实际的处理速率高于配置的速率,充分利用系统资源。
漏斗算法一般用于保护第三方的系统,比如自身的系统需要调用第三方的接口,为了保护第三方的系统不被自身的调用打垮,便可以通过漏斗算法进行限流,保证自身的流量平稳的打到第三方的接口上。
维护一个计数器,开始处理请求时计数器+1,请求处理完毕后计数器-1
优点:实现简单,Java中原子类Atomic即可实现,分布式场景采用Redis incr
缺点:不能应对突发流量
计数器+定时更新
缺点:固定窗口临界问题,假设接口每秒的限流为100,但在0.55s~1.05s的0.5s中涌入200个请求可能会导致限流为100/s的系统崩溃
记录时间窗口中每个请求到来的时间,保证任意时间窗口中请求数量均不超过系统限制
缺点:无法解决短时间内集中流量的冲击
boolean limiter() {
long now = currentTimeMillis(); // 获取当前时间
long counter = getCounterInTimeWindow(now) // 根据当前时间获取时间窗口内的计数
if (counter < threshold) {
// 小于阈值
addToTimeWindow(now); // 记录当前时间
return true;
}
return false;
}
使用队列保存全部request,新的request放入队列尾部,队列满时将其丢弃
优点
缺点
定速的往桶内放入令牌,令牌数量超过桶的限制,丢弃。请求来了先向桶内索要令牌,索要成功则通过被处理,反之拒绝
rate/limiter.go中限流器的实现
令牌桶只要桶中还有 Token,请求就还可以一直进行。当突发量激增到一定程度,则才会按照预定速率进行消费。
type Limiter struct {
mu sync.Mutex
limit Limit // 控制产生令牌的速度 float64的别名
burst int // 令牌桶的最大容量
tokens float64 // 令牌数量
last time.Time // 上次更新令牌桶的时间
lastEvent time.Time // 上次发生限速器事件的时间
}