当前位置:   article > 正文

6种限流方法之服务端时间窗口算法(结合redis)

时间窗口算法

 原文地址:开发者导航 · 你想要的,我全都有!

时间窗口算法

又名滑动时间算法,所谓的滑动时间算法指的是以当前时间为截止时间,往前取一定的时间,比如取60s的时间,在这60s时间内最大的访问数为100。此时算法的执行逻辑为,先清除这60s 之前的所有请求记录,再计算当前集合内请求数是否大于设定的最大请求数100,如果大于100则执行限流拒绝策略,否则插入本次请求记录并返回可以正常执行的标识给客户端。

滑动时间窗口如下图所示:

其中每一个小格子代表10s,被红色虚线包围的时间段则为需要判断的时间间隔,比如60s允许100次请求,那么红色虚线部分则为60s。

如何实现?

借助Redis的有序集合ZSet来实现时间窗口算法限流,实现的过程是:

第一步:先使用ZSet的key存储限流的ID,score用来存储请求的时间。

第二步:每次有请求访问来了之后,先清空之前时间窗口的访问量,统计目前时间窗口的个数与最大允许访问量对比。

第三步:如果大于等于最大访问量则返回 false 执行限流操策略,负责允许执行业务逻辑,并且在 ZSet 中添加一条有效的访问记录。

代码实操

我们借助 Jedis 包来操作 Redis,首先在pom.xml中添加Jedis的jar包:

1

2

3

4

5

6

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->

<dependency>

    <groupId>redis.clients</groupId>

    <artifactId>jedis</artifactId>

    <version>3.3.0</version>

</dependency>

具体的 Java 实现代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

package com.example.demo;

import redis.clients.jedis.Jedis;

/**

 * 6种限流方法之服务端时间窗口算法

 *

 * @author www.codernav.com

 */

public class RedisLimit {

    /**

     * Redis 操作客户端

     */

    static Jedis jedis = new Jedis("127.0.0.1"6379);

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 100; i++) {

            boolean res = isPeriodLimiting("jiagou1216.com"310);

            if (res) {

                System.out.println("正常访问【https://www.codernav.com】:" + i);

            else {

                System.out.println("限流访问【https://www.codernav.com】:" + i);

            }

        }

        // 休眠 4s

        Thread.sleep(4000);

        // 超过最大执行时间之后,再从发起请求

        boolean res = isPeriodLimiting("jiagou1216.com"310);

        if (res) {

            System.out.println("休眠后,正常执行请求");

        else {

            System.out.println("休眠后,被限流");

        }

    }

    /**

     * 限流方法(滑动时间算法/时间窗口算法)

     *

     * @param key      限流标识

     * @param period   限流时间范围(单位:秒)

     * @param maxCount 最大运行访问次数

     * @return 是否限流

     */

    private static boolean isPeriodLimiting(String key, int period, int maxCount) {

        // 当前时间戳

        long nowTs = System.currentTimeMillis();

        // 删除非时间段内的请求数据(清除老访问数据,比如 period=60 时,标识清除 60s 以前的请求记录)

        jedis.zremrangeByScore(key, 0, nowTs - period * 1000);

        // 当前请求次数

        long currCount = jedis.zcard(key);

        if (currCount >= maxCount) {

            // 超过最大请求次数,执行限流

            return false;

        }

        // 未达到最大请求数,正常执行业务,请求记录 +1

        jedis.zadd(key, nowTs, "" + nowTs);

        return true;

    }

}

运行main方法

这种方式有两个缺点:

1)使用 ZSet 存储有每次的访问记录,如果数据量比较大时会占用大量的空间,比如60s允许100W访问时;

2)此代码的执行非原子操作,先判断后增加,中间空隙可穿插其他业务逻辑的执行,有可能导致结果不准确。

谷歌/百度/微信搜索【开发者导航】网站或公众号,各类源码无套路,了解一下?

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

闽ICP备14008679号