Snetinel的滑动时间窗口是如何统计QPS的

基本理论:

首先我们要明确:任意时刻的QPS是指过去一秒内产生的请求个数。所以可以推导出,如果要获得时间轴上X位置上的QPS,应该统计的是在[X-1000ms,X)这个时间范围通过的请求数。


所以,我们应该这么做(毫秒为维度):

每一次请求过来,我使用当前时间毫秒数在内存中查找是否存在一个统计请求个数的原子计数器,如果没有则创建并且计数器+1。当在X时刻,需要统计QPS时,我可以获得[X-1000ms,X)这个区间内所有存在的计数器做累加,就可以获得X时刻的QPS了。

这样做的问题在于:粒度太细了,在系统本身流量比较大的情况下,每秒可能就会产生几百个计数器,因为我们要限制QPS,所以每次请求都需要做统计去和设置的阈值进行比较来判断本次请求应该被拦截或者放行。

所以我们做下优化:不用每一个毫秒刻度对应一个计数器,可以把时间窗口分成很多个段(sentinel里面叫Bucket),每个分段一个计数器,如下图所示:

我把[X-1000,X)这一秒的时间窗口分为了四段,也就是说每段为250ms。当请求Request-1过来的时候,可以计算得到它处于A段,和前面的做法一样,我们先判断A段的计数器是否存在,不存在则创建并且计数器+1,这个时候我需要统计QPS的话,只需要往前再拿三个段的计数器,加上本身所在这个段的计数器求和就行了(也就是A,B,C,D这四个段,Request-2过来的时候,就统计B,C,D,E这四个段)。这样对资源的消耗可以大大的减少。它的缺点就在于,如果段越宽,粒度就越粗,统计的QPS就会偏差越大(sentinel默认为两段,每段500ms)。

时间是永恒的,如果把每一秒的统计数据都放在内存,内存只会被无限消耗。并且,时刻X的QPS只需要前一秒的数据就好了,至于一秒之前的数据,则可以丢弃不用或者归档用于监控等。所以我们需要清理过期的段,清理的方法很多,这里介绍一下sentinel是怎么做的。

sentinel把一秒钟的长度设计成一个圆环形,类似钟表,钟表只能显示24小时,而sentinel的圆环表示了一秒钟。

当Request进来时,首先确定这个request应该处于哪个段(比如我们可以通过当前时间戳的后三位[0,999]来确定)。

我们找到段A后,首先判断当前段的实例,是否存在,如果不存在,则创建,并记录当前段的开始时间X。

如果存在,则判断当前段是否已经过期(因为不仅是X+100会落到A段,X-1000+100也会落到A段),过期则重新创建并更新当前段的开始时间。

统计时也需要判断段是否过期,过期的则不要统计。

posted @ 2020-08-01 20:56  啥也不懂的新同学  阅读(621)  评论(0编辑  收藏  举报