常见的限流方法有计数器、漏斗算法、令牌桶算法,各自也有各自的优缺点
一 计数器限流方法
计数器算法比较简单,即一段时间内的调用次数是有上限的,当大于最大请求次数,则拒绝服务
计数器算法伪代码:
public class CounterDemo {
public long timeStamp = getNowTime();
public int reqCount = 0;
public final int limit = 100; // 时间窗口内最大请求数
public final long interval = 1000; // 时间窗口ms
public boolean grant() {
long now = getNowTime();
if (now < timeStamp + interval) {
// 在时间窗口内
reqCount++;
// 判断当前时间窗口内是否超过最大请求控制数
return reqCount <= limit;
} else {
timeStamp = now;
// 超时后重置
reqCount = 1;
return true;
}
}
}
优点:原理简单,利于实现
缺点:
- 不能很好地防止瞬时流量,在01-02秒之间,请求假如都在1.1秒处进来,导致瞬时流量很大,所以使用计数器方法时,要预估好服务的瞬时承载能力
-
不能避免抖动问题
如上图,假设1秒内最大请求数为100,所以在1-2秒之间最多只能有100次,在02秒时请求次数会被重置为0,重新计算,这样极端情况会在存在在1.5-2.5秒之间的请求总数是200
二 漏斗限流方法
漏斗算法思想是将所有请求先存到一个桶里。若此刻桶容量没满,表示当前请求是可以访问资源。若满了,则拒绝服务。同时桶会以固定速率取出桶里的请求来处理
具体实现方法可以将请求先暂存到一个队列中,若队列已满,则拒绝该请求。同时有一个周期性定时任务来消费队列里的数据
从实现可以看出,不管请求有多少或者瞬时流量有多大,请求的处理是固定速率的,所以令牌桶油流量整形的功能,具体实现代码可以参考:限流-令牌桶实现(go版本)
相对计数器方法,令牌桶能有效避免抖动的问题,但当瞬时请求量很大时,后续的请求很有可能由于得不到及时处理而超时
三 令牌桶方法
当一个请求进来时,它要先获取一个token,若获取成功,则可以访问,若不成功,则会拒绝服务。存放令牌的地方就是令牌桶,令牌桶中的令牌会以固定速率生成
具体的令牌桶伪代码:
public class TokenBucketDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 令牌放入速度
public int tokens; // 当前令牌数量
public boolean grant() {
long now = getNowTime();
// 先添加令牌
tokens = min(capacity, tokens + (now - timeStamp) * rate);
timeStamp = now;
if (tokens < 1) {
// 若不到1个令牌,则拒绝
return false;
}else {
// 还有令牌,领取令牌
tokens -= 1;
return true;
}
}
}
令牌桶能够承载一定的瞬时流量,并且也有一些广泛的应用,如Guava中基于令牌桶算法实现的限流器,阿里出品的sentinel
参考: