计数器
我们知道,限流算法最简单的就是计数器法,当时间间隔超过设定的k秒后,就拒绝请求,简单的伪代码就是:
//k为设定的时间间隔
if(now < (start_time + k))
{
// k 秒内小于阈值
if(capacity < ioslevel)
capacity++;
else
reject ;
}
else{
start_time = now;
capacity = 0;
}
但是这样的话,就会导致:假设时间间隔为1秒,那么在1秒末突然请求到达阈值,而下一秒初的请求也达到阈值,那么在这段时间内,请求数是超过1秒设定的阈值。
滑动窗口
从而改进算法就是,基于滑动窗口,保证任意请求的1秒间隔内的容量不超过设定的阈值
看下图
从而,我们可以基于循环队列这种数据结构来实现,刚开始,实现的时候,被绕的有点晕,感觉对循环队列每太明白,并且对于与限流的滑动窗口没有结合好,浪费了很多时间
思路:
//tail指向最新时间
//head指向最旧时间
//超过时间间隔
if(time_interval >= k)
//将最旧时间向前移
//将最新时间存储到tail中
tail = (tail + 1) % size;
queue[tail] = now;
head = (head + 1) % size;
else //未超过时间间隔
//若为超过容量,存储最新时间
if((tail + 1) % size != head)
tail = (tail + 1) % size;
queue[tail] = now;
else
reject;
下面是实现code,注意我这里为了保证移动正确,在head、tail初始化时,表示第一个容量已经被用了,当第一个请求过来的时候将放在1的位置,而不是最初0的位置(这么做是为了我的算法实现正确)
//容量设为了10
class SKI_Window{
public:
// 初始化
SKI_Window(int second) :time_interval(0),head(0),tail(0),k(second) {
que[0] = time(0);
};
void skiWindow()
{
time_t now = time(0);
tm * now_s = gmtime(&now);
int sec = now_s->tm_sec;
int min = now_s->tm_min;
//cout << ctime(&now) << endl;
//cout << ctime(&que[head]) << endl;
tm *s_stamp = gmtime(&que[head]);
int s_sec = s_stamp->tm_sec;
int s_min = s_stamp->tm_min;
// 秒的间隔,只计算了最多分钟的差别,没有计算小时的
time_interval = (min-s_min)*60+ sec - s_sec;
int check = 0;
if (time_interval >= k)
{
tail = (tail + 1) % 10;
que[tail] = now;
head = (head + 1) % 10;
cout << "timeout timeout timeout timeout timeout!!!!!!!!!!!!" << endl;
}
else
{
// 没超过队列就一直向前走
if ((tail + 1) % 10 != head)
{
tail = (tail + 1) % 10;
que[tail] = now;
}
else
{
cout << "request invalid" << endl;
}
}
//cout << "interval: " << time_interval << endl;
//cout << "#####################" << endl;
}
private:
int time_interval;
time_t que[10];
int head ;
int tail ;
int k;
};
漏桶算法
思路:计算两次请求之间应该倒出多少水,从而获得一些容量,最大不超过漏斗容量;
当请求过来时,与可使用容量进行比较,当有可使用容量时,就允许请求,此时可使用容量减1,当不可使用时(可使用容量为0),拒绝请求。
class FunnelRateLimiter {
private:
// 容量
int capacity;
// 每秒漏水的速度
double leakingRate;
// 漏斗没有被占满的体积
int emptyCapacity;
// 上次漏水的时间
time_t lastLeakingTime ;
// 时间间隔
int time_interval;
public:
FunnelRateLimiter(int c, int speed) :capacity(c), leakingRate(speed),lastLeakingTime(time(0)),time_interval(0) {
emptyCapacity = c;
};
FunnelRateLimiter(int capacity, double leakingRate) {}
void makeSpace() {
time_t now = time(0);
tm * now_s = gmtime(&now);
int sec = now_s->tm_sec;
int min = now_s->tm_min;
tm *s_stamp = gmtime(&lastLeakingTime);
int s_sec = s_stamp->tm_sec;
int s_min = s_stamp->tm_min;
// 秒的间隔,没有按微秒计算产生令牌数
time_interval = (min - s_min) * 60 + sec - s_sec;
// 计算离上次漏斗的时间到现在漏掉的水
int deltaQuota = time_interval * leakingRate;
// 更新上次漏的水
lastLeakingTime = now;
// 更新腾出的空间
emptyCapacity += deltaQuota;
// 超出最大限制 复原
if (emptyCapacity > capacity) {
emptyCapacity = capacity;
}
}
boolean isActionAllowed() {
makeSpace();
// 如果有可使用容量
if (emptyCapacity > 0) {
// 给腾出空间注入流量
emptyCapacity--;
return true;
}
return false;
}
};
令桶牌
漏斗算法会以一个稳定的速度转发,而令牌桶算法平时流量不大时在“攒钱”,流量大时,可以一次发出队列里有的请求,而后就受到令牌桶的流控限制。
另外,令牌桶还可能做成第三方的一个服务,这样可以在分布式的系统中对全局进行流控。
- 有一定容量的一个桶,按照一定速率来产生令牌
- 两次请求的时间间隔下产生的令牌超过容量,那么最多只产生该容量大小的令牌
- 每次请求都消耗掉一个令牌
- 当桶中没有令牌就拒绝请求
class Buckets
{
public:
Buckets(int c,int n):capacitys(c),timestamp(time(0)),time_interval(0),nums(n) {
timestamp = time(0);
};
void compute_tokens()
{
time_t now = time(0);
tm * now_s = gmtime(&now);
int sec = now_s->tm_sec;
int min = now_s->tm_min;
tm *s_stamp = gmtime(×tamp);
int s_sec = s_stamp->tm_sec;
int s_min = s_stamp->tm_min;
// 秒的间隔,没有按微秒计算产生令牌数
time_interval = (min - s_min) * 60 + sec - s_sec;
//最多能产生多少个令牌
int token = min(capacitys, tokens+time_interval * nums);
timestamp = now;
//还有令牌
if (token-->0)
{
cout << "allow request!!!" << endl;
tokens = token;
}
else
{
tokens = 0;
cout << "reject request!!!" << endl;
}
}
private:
int capacitys;
time_t timestamp;
int tokens;
int time_interval;
};