当前位置:   article > 正文

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

小电流的滑动窗口优化校表算法

3a517f6a3467d9e97f6ee3f144227524.png

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

为数据可视化赋能Spring Boot Admin - 第297篇

超实用的康奈尔笔记法

「定制Spring Boot Admin UI的页面」- 第298篇

国内最全的Spring Boot系列之三

版本号命名的前世今生- 值得收藏 - 第299篇

「世界上最好的学习法:费曼学习法」

高并发,不怕不怕「限流算法第一把法器:计数器法」 - 第300篇

一、回顾:计算器算法存在问题

对于秒级以上的时间周期来说,会存在一个非常严重的问题,那就是临界问题。

c34696c8ea09dd02667c30310049f6ce.png

从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

师傅:不错,那么今天就说说如何解决这个由于问题。

计算器的问题其实是因为我们统计的精度太低(统计的1秒内的流量情况,只有1个值进行记录)。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。

二、滑动窗口算法

滑动窗口,又称rolling window。为了解决计数器法统计精度太低的问题,引入了滑动窗口算法。下面这张图,很好 地解释了滑动窗口算法:

ffb5ab711616f13d2f4ad0ec5a2c5606.png

在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求 在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?在上图中,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘×××的格子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触发了限流。

回顾一下上面的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

BTW:

(1)滑动窗口算法是以当前这个时间点为基准,往前推移1秒进行计算当前1秒内的请求量情况。
(2)滑动窗口限流统计的精准度是由划分的格子多少决定的,这个怎么理解呐,就是把1秒中进行划分成多个时间段,比如2个格子的话,那么就是2段,0-500ms和501-1000ms。那么就会两个值进行存储统计请求量,比如数组 [0,1] 各存储一个段的请求值。
(3)计算器算法是滑动窗口算法将时间段划分为1的特殊情况。

综上我们对滑动窗口算法下个定义:滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。

三、滑动窗口算法实现

我们看一段代码:

  1. /**
  2. * 滑动时间窗口限流实现
  3. * 假设某个服务最多只能每秒钟处理100个请求,我们可以设置一个1秒钟的滑动时间窗口,
  4. * 窗口中有10个格子,每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务请求的次数
  5. */
  6. public class SlidingTimeWindow {
  7. // 时间窗口内最大请求数
  8. public final int limit = 100;
  9. // 服务访问次数
  10. Long counter = 0L;
  11. // 使用LinkedList来记录滑动窗口的10个格子。
  12. LinkedList<Long> slots = new LinkedList<Long>();
  13. // 时间划分多少段落
  14. int split = 10;
  15. // 是否限流了,true:限流了,false:允许正常访问。
  16. boolean isLimit = false;
  17. private void doCheck() throws InterruptedException {
  18. while (true) {
  19. slots.addLast(counter);
  20. if (slots.size() > split) {
  21. slots.removeFirst();// 超出了,就把第一个移出。
  22. }
  23. // 比较最后一个和第一个,两者相差100以上就限流
  24. if ((slots.peekLast() - slots.peekFirst()) > limit) {
  25. System.out.println("限流了。。");
  26. // 修改限流标记为true
  27. isLimit = true;
  28. } else {
  29. // 修改限流标记为false
  30. isLimit = false;
  31. }
  32. Thread.sleep(1000/split);
  33. }
  34. }
  35. /**
  36. * 测试
  37. * @param args
  38. * @throws InterruptedException
  39. */
  40. public static void main(String[] args) throws InterruptedException {
  41. SlidingTimeWindow timeWindow = new SlidingTimeWindow();
  42. //开启一个线程判断当前的限流情况.
  43. new Thread(new Runnable() {
  44. @Override
  45. public void run() {
  46. try {
  47. timeWindow.doCheck();
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. }
  52. }).start();
  53. //模拟请求.
  54. while(true) {
  55. //判断是否被限流了.
  56. if(!timeWindow.isLimit) {
  57. timeWindow.counter++;
  58. //未被限流执行相应的业务方法.
  59. // executeBusinessCode();
  60. //模拟业务执行方法时间.
  61. Thread.sleep(new Random().nextInt(15));
  62. System.out.println("业务方法执行完了...");
  63. }else {
  64. System.out.println("被限流了,直接返回给用户");
  65. }
  66. }
  67. }
  68. }

说明:

(1)doCheck方法,就是改变是否限流标志位的方法:这里通过LinkedList(链表)来进行模拟,通过LinkedList的大小来控制1秒分隔多少段。peekLast()、slots.peekFirst() 就是这个时间窗口的一个流量值。

(2)在main方法中,我们通过开启一个线程来执行我们的doCheck()方法,由于doCheck()方法是while(true),所以会一直执行,sleep的时间刚好是每个时间间隔值,那么就会不断刷新LinkedList中的值。

(3)最后while(true)代码是模拟业务执行调用代码,也就是controller发起调用的代码了。

四、悟纤小结

师傅:徒儿,师傅这代码都敲的腰酸背痛了。

悟纤:那师傅赶紧早点回去休息吧。

师傅:(内心世界:徒儿这是没听明白我的意思,有句话说重要的事情要说3遍)徒儿,师傅腰酸背痛~ 腰酸背痛~ 腰酸背痛~。

悟纤:师傅,我懂了,您这是要让我给您敲敲背是吧,这您不找人给你按摩去,找我这个不专业的,按着也不舒服。

师傅:就徒儿顽皮,不按就不按,看看你认真听没有,小结下这节的内容。

(1)滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。

(2)计算器算法也属于滑动窗口算法,是滑动窗口算法的一种情况(划分周期为1)。

(3)滑动窗口算法实现方式:数组(多少周期,数组大小就是多大,将每个时间通过一定的算法落到每个数组下标即可);链表(通过链表中的大小来判断划分周期个数,如果超过了,可以使用链表的removeFirst()移除第一个,从而实现往后移动窗口)

(4)数组实现的实例:这个可以看Sentinel的源码,就是定义了一个数组来实现的,时间划分是2。

五、小思考

当在系统刚刚启动的时候,系统很多资源都未分配完成,瞬间来了n个请求,如果使用滑动窗口算法的话,那么只有达到了QPS控制阈值,才会触发限流机制,那么这样针对系统刚启动资源未分配的情况,就无法防止系统瘫痪了。

针对上面的这种情况,那么你有什么对应的限流算法可以应对呐?

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/274832
推荐阅读
相关标签
  

闽ICP备14008679号