Redis--Zset使用场景举例(滑动窗口实现限流)

前言

什么是滑动窗口

  • 滑动窗口是一种流量控制策略,用于控制一定时间内请求的访问数量。

  • 其原理是:将时间划分成规定的时间片段,每个片段有固定的时间间隔,如1s,1min,1h,然后定义一个时间窗口,比如5s,5min等,该窗口会随着时间向右移动。此外还需要计数器计算窗口内的请求数。当窗口移动时,会把已经走过的时间片段的请求数删掉。每当请求进入系统时,会检查计数器中的请求数是否已经满了,如果计数未满,则请求允许被执行;否则执行相应的拒绝方法。

    在这里插入图片描述

  • 滑动窗口在时间内平滑地控制流量,而非简单地固定请求数与速率,可以更加灵活地突发流量和峰值流量。

zset实现滑动窗口

  • 在redis中可以使用zset实现滑动窗口作为限流方案,假如接口A每一分钟只能访问100次,那么我们可以将这个需要限流的接口名作为key,value采用zset数据结构,zset的score设置为当前请求的时间戳,zset的member只需要保证唯一性即可。

  • 涉及到的zset指令

    向zset添加数据:zadd key score member
    删除zset某个score范围内的数据: zremrangebyscore key min max
    统计zset中数据的数量:zcard key

  • 代码实现:在代码中定义滑动窗口大小为"windowSize",收到请求后,在redis生成zset,用zremrangebyscore删除score小于当前时间戳减去"windowSize"的数据,使用zcard查询当前zset中的数据量,即请求量判断是否超出限制值,若超出则不加入zset。

    public class RedisRateLimiter {
         
        private Jedis jedis;
        private String key;
        //窗口大小
        private int windowsize;
        //限制访问的请求数
        private Integer limitValue;
    
        public RedisRateLimiter(Jedis jedis, String key, int windowsize, Integer limitValue) {
         
            this.jedis = jedis;
            this.key = key;
            this.windowsize = windowsize;
            this.limitValue = limitValue;
        }
    
        public boolean allowVisit() {
         
            //获取当前时间戳
            long nowTimeStamp = System.currentTimeMillis();
            //窗口开始时间为当前时间戳减去60s
            long windowStartTime = nowTimeStamp - windowsize * 1000;
            //删除score小于窗口开始时间的数据
            jedis.zremrangeByScore(key, "-inf", String.valueOf(windowStartTime));
            if (jedis.zcard(key) < limitValue) {
         
                jedis.zadd(key, nowTimeStamp, String.valueOf(nowTimeStamp));
                return true;
            }
            //超过limieValue 返回false
            return false;
        }
    
        /**
         * 上面的方法可以改写为使用lua脚本,以避免高并发情况下的原子性问题
         */
        public boolean allowVIsitUseLua() {
         
            //获取当前时间戳
            long nowTimeStamp = System.currentTimeMillis();
            String luaScript = """
                        local window_start_time = ARGV[1] -ARGV[3]*1000
                        redis.call('ZREMRANGEBYSCORE',KEYS[1],'-inf',window_start_time)
                        local now_request = redis.call('ZCARD',KEYS[1])
                        if now_request < tonumber(ARGV[2]) then
                             redis.call('ZADD',KEYS[1],ARGV[1],ARGV[1])
                             return 1
                        else
                             return 0
                        end
                    """;
            Object result = jedis.eval(luaScript, 1, key, String.valueOf(nowTimeStamp), String.valueOf(limitValue), String.valueOf(windowsize));
            return (long) result == 1;
        }
    
        public static void main(String[] args) throws InterruptedException {
         
            Jedis jedis = new Jedis("127.0.0.1");
            String key = "interfaceA";
            jedis.del(key);
            RedisRateLimiter interfaceA = new RedisRateLimiter(jedis, key, 60, 10);
            //调用20次接口观察结果
            for (int i = 0; i < 20; i++) {
         
                System.out.println("当前时间:"+ DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss:SSS").format(LocalDateTime.now())+
               "接口访问情况: "+(interfaceA.allowVIsitUseLua()?"成功":"失败"));
                Thread.sleep(1000);
            }
        }
    }
    
  • 测试结果:我们在mian方法中,调用20次接口A,设置滑动窗口为60秒内只可以访问10次,观察接口A的访问情况:
    在这里插入图片描述

  • 观察运行结果,因为60秒内该接口只能调用10次,所以调用20次接口A,只有前10次成功了,与我们的期望相同。到此我们通过zset实现了滑动窗口限流的功能。

小结

本文通过Redis的有序集合Zset实现了滑动窗口限流的功能。然而这个方案也存在着缺点,因为zset要记录滑动窗口内的所有接口记录,当我们的要求是某接口在60秒内只能访问100万次,那么我们就可能得存入100万条记录,这种情况下,采用这种方案会消耗很大的存储空间,明显不适用。

附录

  • 在window系统快速使用Redis服务,只需要下载该压缩包 redis压缩包:redis.7z,解压后,找到redis-server.exe即可启动redis服务。

相关推荐

  1. 分布式——Redis + Lua实现滑动窗口算法

    2024-01-20 19:20:03       41 阅读
  2. 使用 Redis + Lua 实现分布式

    2024-01-20 19:20:03       39 阅读
  3. Redis实现

    2024-01-20 19:20:03       58 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-20 19:20:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-20 19:20:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-01-20 19:20:03       87 阅读
  4. Python语言-面向对象

    2024-01-20 19:20:03       96 阅读

热门阅读

  1. 【nginx】405 not allowed问题解决方法

    2024-01-20 19:20:03       61 阅读
  2. 创建conda环境

    2024-01-20 19:20:03       64 阅读
  3. 【无标题】

    2024-01-20 19:20:03       54 阅读
  4. 一分钟带你学会Python变量与数据类型

    2024-01-20 19:20:03       62 阅读
  5. 使用阿里云服务器自建数据库配置多大合适?

    2024-01-20 19:20:03       73 阅读
  6. /*局部变量与静态局部变量的区别*/

    2024-01-20 19:20:03       64 阅读
  7. Vue改变数据,页面不刷新的问题

    2024-01-20 19:20:03       70 阅读