限流之滑动窗口算法实战

x33g5p2x  于2022-01-11 转载在 其他  
字(2.1k)|赞(0)|评价(0)|浏览(437)

一 算法

滑动窗口算法弥补了计数器算法的不足。滑动窗口算法把间隔时间划分成更小的粒度,当更小粒度的时间间隔过去后,把过去的间隔请求数减掉,再补充一个空的时间间隔。

如下图所示,把1分钟划分为10个更小的时间间隔,每6s为一个间隔。

1 一个时间窗口为1分钟,滑动窗口分成10个格子,每个格子6秒。

2 每过6秒,滑动窗口向右移动1个格子。

3 每个格子都有独立的计数器。

4 如果时间窗口内所有的计数器之和超过了限流阀值,则触发限流操作。

如下图所示,滑动窗口算法比计数器算法控制得更精细。

用户在0:59 时刻发送了100个请求,第10个格子的计数器增加100,下一秒的时候时间窗口向右移动1格,这时再来100个请求就超过了阈值,不会处理这100个请求,这样就避免了计数器场景出现的问题。

滑动窗口设置得越精细,限流的效果越好,但滑动窗口的时间间隔(小格子)多了,存储的空间也会增加。

二 需求

1 设计一个滑动窗口,窗口有10个格子,每个格子10秒,每隔10秒移动一格。

2 装满所有格子的时间为 10 * 10 = 100 秒。也就是说时间窗口是 100 秒。

3 从100秒开始,开始滑动,新请求数开始覆盖老请求数。

三 代码

  1. package currentLimit;
  2. import java.util.Date;
  3. import java.util.Random;
  4. /**
  5. * @className: CounterLimit
  6. * @description: 滑动窗口算法
  7. * @date: 2022/1/8
  8. * @author: cakin
  9. */
  10. public class SlideWindowLimit {
  11. // 滑动窗口大小
  12. static final int size = 10;
  13. // 滑动窗口数组,每移动一个格子,更新对应数据项的值
  14. static int window[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  15. // 理解为移动窗口中正在计数的格子
  16. static int curId = 0;
  17. // 记录上次统计时间
  18. static Date lastDate = new Date();
  19. // 当前窗口计数总和
  20. static int counter = 0;
  21. /**
  22. * 功能描述:模拟一次请求是否限流
  23. *
  24. * @return true:限流 false;不限流
  25. * @author 贝医
  26. * @date 2022/1/8
  27. * @description:
  28. */
  29. static boolean slideWindowLimit() {
  30. // 获取当前时间
  31. Date now = new Date();
  32. // 当前时间同上次记录时间的间隔,单位为秒
  33. long time = (now.getTime() - lastDate.getTime()) / 1000;
  34. // 按照新的移动窗口进行计数
  35. if (time >= 10) {
  36. // 当前计数格子的下一个格子将被清掉重写
  37. curId++;
  38. curId = curId % size;
  39. int newCurId = curId;
  40. // 下一个格子将被清掉,总数据减掉
  41. counter = counter - window[newCurId];
  42. // 新格子设置为1
  43. window[newCurId] = 1;
  44. // 记录滑动的时间
  45. lastDate = now;
  46. } else {
  47. // 当前计数的格子
  48. ++window[curId];
  49. }
  50. ++counter;
  51. return counter >= 1000;
  52. }
  53. // 测试方法
  54. public static void main(String[] args) {
  55. for (; ; ) {
  56. // 模拟一秒
  57. try {
  58. Thread.sleep(1000);
  59. } catch (InterruptedException e) {
  60. e.printStackTrace();
  61. }
  62. Random random = new Random();
  63. int i = random.nextInt(3);
  64. // 模拟1秒内请求8次
  65. if (i == 1) {
  66. for (int j = 0; j < 8; j++) {
  67. if (slideWindowLimit()) {
  68. System.out.println("限流了" + counter);
  69. } else {
  70. System.out.println("没限流" + counter);
  71. }
  72. }
  73. } else if (i == 2) { // 模拟1秒内请求9次
  74. for (int j = 0; j < 9; j++) {
  75. if (slideWindowLimit()) {
  76. System.out.println("限流了" + counter);
  77. } else {
  78. System.out.println("没限流" + counter);
  79. }
  80. }
  81. } else { // 模拟1秒内请求10次
  82. for (int j = 0; j < 10; j++) {
  83. if (slideWindowLimit()) {
  84. System.out.println("限流了" + counter);
  85. } else {
  86. System.out.println("没限流" + counter);
  87. }
  88. }
  89. }
  90. }
  91. }
  92. }

四 测试

五 说明

记录滑动窗口中的请求数。滑动窗口中的请求数控制在 1000以内。滑动窗口能记录100秒的请求,所以如果每秒请求不超过10,不会限流。测试用例也是这样设计的,每秒模拟发送的请求为8次,9次,10次。从测试结果来看,符合预期。

相关文章