给接口上“保险丝”(rate-limiter)
可能到来的某天
某系统接口的请求量暴增,继而接口不可用,并引发连锁反应导致整个系统不能正常服务。
优化程序执行效率?换更power的机器?扩大集群?
无论系统接口处理能力多强,也难免会有请求方突发性或者无限制性调用,导致系统接口崩溃。所以,在接口能力满足预期业务下,给接口上“保险丝”(限流),是系统持续稳定、自我保护的一个机制。
如何限流
控制好接口单位时间内的请求数(QPS/TPS)。
系统上线前,一般都需要对系统进行压测,确认系统吞吐量或者各个接口的处理能力。我们可以根据测试报告,对系统每个接口设定rate进行限流(当然,也可以细化粒度,针对调用方和调用接口限流,有点像公共api接口或者能力平台接口的接口调用限制)
如何实现限流
计数器(简单粗暴)
简单维护一个单位时内的计数器,请求过来则递增,计算器过期则清理。
伪代码:
long timeStamp=getNowTime();
int reqCount=0;
const int rate=100;//时间周期内最大请求数
const long interval=1000;//时间控制周期ms
bool grant(){
long now=getNowTime();
if (now <timeStamp+interval){//在时间控制范围内
reqCount++;
return reqCount>rate;//当前时间范围内超过最大请求控制数
}else{
timeStamp=now;//超时后重置
reqCount=0;
return true;
}
}
漏桶算法(Leaky Bucket)
水(请求)先进入到漏桶里,漏桶以恒定的速度出水,当水流入速度过大会导致溢出。
伪代码:
long timeStamp=getNowTime();
int capacity; // 桶的容量
int rate ; //水漏出的速度
int water; //当前水量
bool grant() {
//先执行漏水,因为rate是固定的,所以可以认为“时间间隔*rate”即为漏出的水量
long now = getNowTime();
water = max(0, water- (now - timeStamp)*rate);
timeStamp = now;
if (water < capacity) { // 水还未满,加水
water ++;
return true;
} else {
return false;//水满,拒绝加水
}
}
令牌桶算法(Token Bucket)
以恒定速度往桶里放入Token,请求过来需从桶里获取Token,当请求速度过大会导致获取Token失败。
Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流
伪代码
long timeStamp=getNowTime();
int capacity; // 桶的容量
int rate ; //令牌放入速度
int tokens; //当前水量
bool grant() {
//先执行添加令牌的操作
long now = getNowTime();
tokens = max(capacity, tokens+ (now - timeStamp)*rate);
timeStamp = now;
//令牌已用完,拒绝访问
if(tokens<1){
return false;
}else{//还有令牌,领取令牌
tokens--;
retun true;
}
}
其他算法
基于上述计数器方式,将单位时间切割到更小的时间片,每个时间片维护一个计数器,随着时间推送,清理单位时间外的所有计数器,统计当前单位时间内的所有计数器。
参考
- http://colobu.com/2014/11/13/rate-limiting/
- http://www.cnblogs.com/LBSer/p/4083131.html
- docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/RateLimiter.html
- http://en.wikipedia.org/wiki/Leaky_bucket
- http://sharecore.net/2014/06/21/%E8%BF%87%E8%BD%BD%E4%BF%9D%E6%8A%A4%E7%AE%97%E6%B3%95%E6%B5%85%E6%9E%90/