背景
分布式系统中,由于接口API无法控制上游调用方的行为,因此当瞬时请求量突增时,会导致服务器占用过多资源,发生响应速度降低、超时、乃至宕机,甚至引发雪崩造成整个系统不可用。
限流,Rate Limiting,就是对API的请求量进行限制,对于超出限制部分的请求作出快速拒绝、快速失败、丢弃处理,以保证本服务以及下游资源系统的稳定。
哪些原因会带来瞬时请求量突增?
- 热点业务、突发热点数据带来的激增。例如微博热搜的爆点。
- 上游系统的bug导致。
- 恶意的攻击流量。
实现限流的方法很多,一句话来讲,就是限制每秒钟内API可处理的请求量。常见的有以下几种算法:固定窗口计数法、滑动窗口计数法、漏桶算法、令牌桶算法。
固定窗口(Fixed Window)计数法
原理
固定窗口计数法的思路是:
- 将时间划分为固定的窗口大小,例如1s
- 在窗口时间段内,每来一个请求,对计数器加1。
- 当计数器达到设定限制后,该窗口时间内的之后的请求都被丢弃处理。
- 该窗口时间结束后,计数器清零,从新开始计数。如上图所示,10s内限制1000个请求,在第11s的时候计数器会从0重新开始计数。
特点
优点:实现简单,并且内存占用小,我们只需要存储时间窗口中的计数即可;
缺点:流量曲线可能不够平滑,有“突刺现象”(2N),如下图所示。
- 一段时间内(不超过时间窗口)系统服务不可用。
比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第1ms来了100个请求,然后第2ms-999ms的请求就都会被拒绝,这段时间用户会感觉系统服务不可用。
- 窗口切换时可能会产生两倍于阈值流量的请求。即:瞬时流量的临界问题,在最坏的情况下,会让通过的请求量是限制数量的两倍;
比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第999ms来了100个请求,窗口前期没有请求,所以这100个请求都会通过。再恰好,下一个窗口的第1ms有来了100个请求,也全部通过了,那也就是在2ms之内通过了200个请求,而我们设定的阈值是100,通过的请求达到了阈值的两倍。
实现
public class CounterRateLimiter extends MyRateLimiter {
/**
* 每秒限制请求数
*/
private final long permitsPerSecond;
/**
* 上一个窗口的开始时间
*/
public long timestamp = System.currentTimeMillis();
/**
* 计数器
*/
private int counter;
public CounterRateLimiter(long permitsPerSecond) {
this.permitsPerSecond = permitsPerSecond;
}
@Override
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 窗口内请求数量小于阈值,更新计数放行,否则拒绝请求
if (now - timestamp < 1000) {
if (counter < permitsPerSecond) {
counter++;
return true;
} else {
return false;
}
}
// 时间窗口过期,重置计数器和时间戳
counter = 0;
timestamp = now;
return true;
}
}
滑动窗口(Sliding Window)计数法
背景
计数器滑动窗口算法是计数器固定窗口算法的改进,解决了固定窗口切换时可能会产生两倍于阈值流量请求的缺点。TCP协议中数据包的传输,同样也是采用滑动窗口来进行流量控制。
原理
滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。
当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。
从图中不难看出,滑动窗口算法就是固定窗口的升级版。将计时窗口划分成一个小窗口,滑动窗口算法就退化成了固定窗口算法。而滑动窗口算法其实就是对请求数进行了更细粒度的限流,窗口划分的越多,则限流越精准。
总结滑动窗口计数法的思路是:
- 将时间划分为细粒度的区间,每个区间维持一个计数器,每进入一个请求则将计数器加一。
- 多个区间组成一个时间窗口,每流逝一个区间时间后,则抛弃最老的一个区间,纳入新区间。
- 若当前窗口的区间计数器总和超过设定的限制数量,则本窗口内的后续请求都被丢弃。
范例
滑动窗口计数器限流是在计数器限流的基础上将固定的时间段划分为若干个时间窗口,随着时间的推移,保持时间段内的滑动窗口个数,在常规计数器限流的基础上避免了瞬时流量对服务器的压力。
如上图所示,0-3s内有600请求,8-13s有700请求,当第16s时新增500请求会触发限流。
特点
- 避免了计数器固定窗口算法固定窗口切换时可能会产生两倍于阈值流量请求的问题;
- 当窗口中流量到达阈值时,流量会瞬间切断;
- 在实际应用中我们要的限流效果往往不是把流量一下子掐断,而是让流量平滑地进入系统当中。
实现