计数器滑动窗口的出现,就是为了解决计数器限流方式中在窗口切换时产生2倍于阈值的缺点。
原理
滑动窗口是在计数器限流的固定窗口的基础上,把固定窗口划分为多个小窗口,这些小窗口构成一个环,每个小窗口分别计数,而这些小窗口计数的总和不能超过中的限制,从而保证限流。
同时当请求的时间大于当前窗口的最大值时,进行调整滑动窗口,把滑动窗口先后移动一个或多个小窗口(滑动多少取决于请求时间超过了多个少窗口时间),同时把滑动的窗口计数清空。
从上图可以看到,划分小窗口数量越多,限流则越准确。由于小窗口计数的总和不能超过最大限度,保证了时间轴中每个时间片过程中不会出现限流的最大限度。当然,若不进行小窗口划分,计数器滑动窗口限流也就退化成了计数器固定窗口。
实现
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
long max(long a, long b)
{
return a>b ? a: b;
}
long min(long a, long b)
{
return a>b ? b : a;
}
int gwindowSize; //窗口大小,毫秒为单位
int glimit;//窗口内限流大小
int gsplitNum;//切分小窗口的数目大小
int * counters;//每个小窗口的计数数组
int gindex;//当前小窗口计数器的索引
long startTime;//窗口开始时间
long getCurrentTime()
{
struct timeval stTimeVal;
struct tm *pstTimeCurrent = NULL;
gettimeofday(&stTimeVal, NULL);
pstTimeCurrent = localtime((time_t *)&stTimeVal.tv_sec);
return mktime(pstTimeCurrent);
}
void init(int windowSize, int limit, int splitNum)
{
gwindowSize = windowSize;
glimit = limit;
gsplitNum = splitNum;
counters = (int *)calloc(splitNum , sizeof(int));
gindex = 0;
startTime = getCurrentTime();
return;
}
//把已经超时的小窗口对应的数组置0,也即是进行滑动窗口
void slideWindow(int windowsNum)
{
if(windowsNum == 0)
return ;
//最大也就是把所有的数组清空
int slideNum = min(windowsNum, gsplitNum);
for(int i=0; i<slideNum; i++)
{
gindex = (gindex + 1) % gsplitNum;
counters[gindex] = 0;
}
startTime = startTime + windowsNum * (gwindowSize / gsplitNum);
return ;
}
int IsValid()
{
long now = getCurrentTime();
//获取开始时间至当前时间经过了多少小窗口
int windowsNum = (int)(max(now - startTime - gwindowSize, 0) / (gwindowSize / gsplitNum)); //windowSize / splitNum 表示每个小窗口多长时间
slideWindow(windowsNum); //滑动窗口
int count = 0; //记录当前所有窗口处理的数量
//统计从startTime 到 now这段时间共处理了多少请求
for(int i=0; i < gsplitNum; i++)
{
count += counters[i];
}
//超过限制则限流
if(count >= glimit)
return 0;
else //未超限则处理
{
counters[gindex]++;
return 1;
}
}