滑动时间窗口

先不说sentinel的算法实现,先说什么是滑动时间窗口,
我们在进行限流的时候,比如通过QPS进行限流,那假如我们以秒为单位,举个例子:
我设置了限流规则,qps是10

如果不使用滑动窗口算法,在统计qps的时候,就有可能会有问题,比如:
在0 - 500ms中的请求数是0,但是在500ms - 1000 ms的请求数是10;在1000ms - 1500ms 之间又进来了5个请求,此时只判断了1000ms - 2000 ms之间的请求数
因为没有把1S进行拆分多个窗口,所以在1000ms之后,请求进来的时候,统计的是1000ms - 2000ms之间的请求数
场景1:如果在1100ms的时候,进来了5个请求,此时统计1000ms - 2000ms 之间的QPS,满足10这个阈值,就会让请求进来;但是如果是从1100ms 往前算1000ms的话,实际上应该统计的是 100ms - 1100ms之间 这1S的请求数,此时会发现,100 - 1100ms之间的请求数,早就大于10了
所以,如果不使用滑动窗口算法,其实也没太大影响,只是统计的请求数会不是特别准确,对系统会有一定的压力

下面再来说,使用滑动窗口算法的情况
假如我们把2S拆分为4个窗口,每个窗口是500ms
还是上面的假设:在0 - 500ms中的请求数是0,但是在500ms - 1000 ms的请求数是10
场景1:如果当前1100ms请求进来,有3次请求,滑动时间窗口算法会统计500ms - 1500ms 这1S之内的值,会发现此时这个时间段内,请求数量已经达到10了,就会限流,具体的演变过程是这样的:

在1100ms,请求进来的时候,会计算一下,1100ms应该在哪个窗口中,因为只有两个窗口:

公式1:

1100ms / 500ms = 2
2 % 窗口数量2 = 0

得到当前应该在第0个窗口

接着会比较两个窗口的起始时间
现在0号窗口的起始时间是0ms,那1100ms所在窗口的起始时间怎么计算?

公式2:

1100ms - 1100ms % 500 = 1000ms

此时就会得到:1100ms所在窗口的起始时间是1000ms,但是实际0号窗口的起始时间是0ms
1000 > 0,此时就会把0-500ms这个窗口进行reset,然后将0号窗口的起始时间设置为1000ms


此时再来统计请求数量,就把两个窗口的请求数量累加即可,1100ms在请求进来的时候,会发现,已经达到了10的阈值,就会进行限流

这里只是把1S拆分为两个窗口来举例,如果我们把窗口拆的越多,理论上而言,统计会越准确,但是成本也会越大,因为这些数据存储在内存中,会有一定的消耗

sentinel滑动窗口算法的实现

StatisticNode在初始化的时候,有这么一行代码

private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,IntervalProperty.INTERVAL);第一个参数默认是2,第二个参数是1000ms;public LeapArray(int sampleCount, int intervalInMs) {AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive");AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");this.windowLengthInMs = intervalInMs / sampleCount;this.intervalInMs = intervalInMs;this.intervalInSecond = intervalInMs / 1000.0;this.sampleCount = sampleCount;this.array = new AtomicReferenceArray<>(sampleCount);
}

在ArrayMetric的构造函数中,会初始化一个LeapArray对象,在这个对象内部,维护了一个atomicReferenceArray,这个数组,存放的就是我们所说的窗口
我们可以看到,sentinel默认把1000ms分为了两个时间窗口,每个窗口占500ms

除了上面之外,还维护了一个分钟维度的,一分钟分为了60个窗口,每个窗口1S

private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
这是sentinel所维护的统计类型枚举,比如:时间窗口内请求通过数量、被限流的数量、异常的梳理、成功的数量等
public enum MetricEvent {/*** Normal pass.*/PASS,/*** Normal block.*/BLOCK,EXCEPTION,SUCCESS,RT,/*** Passed in future quota (pre-occupied, since 1.5.0).*/OCCUPIED_PASS
}

我们举其中一个例子:维护当前时间窗口内通过请求的数量;其他类型都是类似的代码

com.alibaba.csp.sentinel.slots.statistic.metric.Metric#addPassWindowWrap<MetricBucket> wrap = data.currentWindow();
wrap.value().addPass(count);

在这个方法中,就两行代码,我们分开说

获取当前时间所在的窗口

data.currentWindow()
这个方法有两层含义:
1.如果当前时间,在目前所维护的窗口内,就返回当期那所维护的窗口
2.如果当前时间,不在所维护的窗口内,就会替换现在的窗口

public WindowWrap<T> currentWindow(long timeMillis) {if (timeMillis < 0) {return null;}// 一、计算当前时间应该所处的窗口对应的下标int idx = calculateTimeIdx(timeMillis);// 二、根据当前时间,按照每个窗口500ms,计算当前时间所处窗口的起始时间long windowStart = calculateWindowStart(timeMillis);// 三、while循环中,有四个场景while (true) {WindowWrap<T> old = array.get(idx);if (old == null) {WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));if (array.compareAndSet(idx, null, window)) {// Successfully updated, return the created bucket.return window;} else {// Contention failed, the thread will yield its time slice to wait for bucket available.Thread.yield();}} else if (windowStart == old.windowStart()) {return old;} else if (windowStart > old.windowStart()) {if (updateLock.tryLock()) {try {// Successfully get the update lock, now we reset the bucket.return resetWindowTo(old, windowStart);} finally {updateLock.unlock();}} else {// Contention failed, the thread will yield its time slice to wait for bucket available.Thread.yield();}} else if (windowStart < old.windowStart()) {// Should not go through here, as the provided time is already behind.return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));}}
}

在这段代码中,有两个计算方法是至关重要的,分别是我标注注释一和二的位置
这两个公式,在上面举例的时候, 有说到过,就不再说明了

private int calculateTimeIdx(/*@Valid*/ long timeMillis) {long timeId = timeMillis / windowLengthInMs;// Calculate current index so we can map the timestamp to the leap array.return (int)(timeId % array.length());
}protected long calculateWindowStart(/*@Valid*/ long timeMillis) {return timeMillis - timeMillis % windowLengthInMs;
}

针对while(true)中的四种场景,我们分别举例,来看下是如何处理的
前提:1000ms划分为两个窗口
场景一:在300ms的时候,第一次请求进来,根据上面两个公式,计算得到idx为0,windowStart为0ms,此时进入while循环,符合第一个判断,对窗口进行初始化

场景二:在400ms的时候,又进来一次请求,此时根据上面两个公式,计算得到idx为0,windowStart为0ms,此时进入while循环,符合第二个判断,return

场景三:在1300ms的时候,进来一次请求,根据上面两个公式,计算得到idx为0,windowStart为1000ms,此时进入while循环,符合第三个判断,对现在idx为0的窗口进行reset,然后设置起始时间是1000ms

场景四:这个场景,在网上看了下,认可比较多的是这样的,在场景三,重新设置了起始时间是1000之后,在1400ms的时候,又进来一个请求,但此时机器的时间被回调了,回调到1S之前,那此时,实际拿到的当前时间是400ms,重新计算之后,发现idx是0,但是起始时间是 0ms,就符合场景四;场景四我理解是一种异常场景,因为场景四是自己new了一个窗口,这个窗口并没有放到维护的窗口数组中,理论上,这个请求不会被统计到

统计数量

public void addPass(int n) {add(MetricEvent.PASS, n);
}public MetricBucket add(MetricEvent event, long n) {counters[event.ordinal()].add(n);return this;
}

这个方法就简单了,就是在当前窗口对象中,维护对应类型的count + 1,即可,这里是通过LongAdder来维护的

统计其他类型的数量也是类似的,只是在最后add(MetricEvent.PASS, n);这个方法中,第一个参数不一样而已

总结

在没有看sentinel源码之前,我理解的滑动时间窗口算法,是不停的在数组中新增一个新的窗口,然后remove第一个窗口,但是看了下sentinel的源码,发现他的处理方式是直接覆盖idx位置的窗口
对时间窗口算法也有一个问题:
举例:
如果在300ms的时候,进来5个请求,然后在600ms的时候,进来了3个请求;第三次请求进来,是在1600ms,那此时岂不是会覆盖idx为1的窗口,在统计的时候,不就统计成 0-500ms 和1000ms - 1500ms两个窗口的请求数量了吗?这块不知道会不会有问题
针对这个问题,我再看源码确认下,哪位大哥有结论,也可以一起探讨下

sentinel滑动时间窗口算法学习相关推荐

  1. sentinel 滑动时间窗口算法

    sentinel 滑动时间窗口的算法 为什么要用滑动时间窗口算法? 互联网中最常见的一个问题:限流,即在一段时间内,限制访问某个接口的请求数. 要实现限流(或熔断降级),方法有很多,最基本的如计数器法 ...

  2. 精度不够,滑动时间来凑「限流算法第二把法器:滑动时间窗口算法」- 第301篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列

  3. Sentinel滑动时间窗限流算法

    Sentinel系列文章 Sentinel熔断限流器工作原理 Sentinel云原生K8S部署实战 Sentinel核心源码解析 时间窗限流算法 如图 10-20这个时间窗内请求数量是60小于阈值10 ...

  4. Sentinel滑动时间窗限流算法原理及源码解析(上)

    文章目录 时间窗限流算法 滑动时间窗口 滑动时间窗口算法改进 滑动时间窗口源码解析 时间窗限流算法 10t到16t 10个请求 16t-20t 50个请求 20t-26t 60个请求 26t到30t ...

  5. Java 实现滑动时间窗口限流算法,你见过吗?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | dijia478 来源 | https://w ...

  6. JAVA实现滑动时间窗口

    先上代码 package demo;import java.time.LocalTime; import java.util.LinkedList; import java.util.List; im ...

  7. Redis(六)——限流算法:滑动时间窗口限流和漏斗限流

    本文主要总结自<redis深度历险> 限流的意义 限流一般是指在一个时间窗口内对某些操作请求的数量进行限制,比如一个论坛限制用户每秒钟只能发一个帖子,每秒钟只能回复5个帖子.限流可以保证系 ...

  8. 【限流算法】java实现固定时间窗口算法

    本文实现了一种基于java的固定时间窗口计数器算法 固定时间窗口计数器算法思想:在固定的时间窗口内,可以允许固定数量的请求进入.超过数量就拒绝或者排队,等下一个时间段进入. 优点:和令牌桶相比,这种算 ...

  9. 时间窗口算法用matlab,一种基于动态规划算法的时间窗口相关的参与者选择方法与流程...

    本发明涉及一种移动群智感知中时间窗口相关的参与者选择方法,特别地,涉及一种基于动态规划算法的时间窗口相关的参与者选择方法. 背景技术: 在移动群智感知网络中,智能设备中嵌入了麦克风.相机.温度传感器. ...

最新文章

  1. 洛谷P1032 字串变换
  2. Vivado使用误区与进阶——XDC约束技巧之I/O篇 (上)
  3. AWS CSAA -- 04 AWS Object Storage and CDN - S3 Glacier and CloudFront(三)
  4. 叙述无保密机制的rsa签名过程_安全系列之——RSA的公钥私钥有多少人能分的清楚?RSA的签名验签与加密解密如何使用公私钥?...
  5. 老单位领导直属领导有恩,新公司薪水给的高,怎么选?
  6. 支持x86架构的通用计算机,X86架构模式
  7. 黑马程序员全套Java教程_Java基础教程_API(十四)
  8. Word:转换PDF
  9. ubuntu 16.04 镜像下载
  10. C++实现 利用前序序列和中序序列构建二叉树
  11. 守望先锋地图英文和英雄英文
  12. 关于自己学习安卓的体会
  13. pandas使用query函数查询dataframe中某一个数据列在指定数据范围的数据行(rows where value is between two values in dataframe)
  14. 分布式系统架构与分布式事务十问十答,小伙伴们赶快收藏吧
  15. 网络设置巨形帧_网卡设置 网卡的高级设置说明
  16. MicroService
  17. 云灾备必不可少,但多云用户怎么办?
  18. Python计算机视觉编程第七章 图像搜索
  19. unity 项目开发——浅谈设计模式的六大原则(一)
  20. html图片拉伸整个页面,css如何让图片拉伸?

热门文章

  1. 碟中谍小组不要太夸张 Avaya要说:哈利法塔的网络很可靠
  2. java实现立方和等式
  3. 如何绘制流程图?绘制流程图在线网站分享
  4. Ubuntu系统管理 ——使用dpkg安装、查看、卸载软件包
  5. 比特率 波特率 数据传输速率 区别;波特率与比特率--转换公式
  6. 牛客SQL 大厂面试真题 某滴打车 6套代码及解析
  7. git冲突“accept theirs”和“accept yours”
  8. 金蝶kis记账王凭证过账要不要要审核
  9. Oracle数据库基本常用命令
  10. 围观饱醉豚与众程序员互怼有感