Redis 实现滑动窗口

作者: 放开那个BUG | 来源:发表于2022-06-24 11:30 被阅读0次

    1、前言

    一般我们做在指定时间内只允许做 n 次都用,一个 key 设置过期时间 t 秒,然后在 key 过期时间内只需要做 n 次。然而这个思路有问题,最明显的就是跨时间段的问题。所以这个问题很显然用滑动窗口来做。

    指定时间T内,只允许发生N次。我们可以将这个指定时间T,看成一个滑动时间窗口(定宽)。我们采用Redis的zset基本数据类型的score来圈出这个滑动时间窗口。在实际操作zset的过程中,我们只需要保留在这个滑动时间窗口以内的数据,其他的数据不处理即可。

    • 每个用户的行为采用一个zset存储,score为毫秒时间戳,value也使用毫秒时间戳(比UUID更加节省内存)
    • 只保留滑动窗口时间内的行为记录,如果zset为空,则移除zset,不再占用内存(节省内存)

    2、代码

    package com.example.demo.redis;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.Pipeline;
    import redis.clients.jedis.Response;
    
    import java.io.IOException;
    
    /**
     * <p>
     *     通过zset实现滑动窗口算法限流
     * </p>
     *
     */
    public class SimpleSlidingWindowByZSet {
    
        private Jedis jedis;
    
        public SimpleSlidingWindowByZSet(Jedis jedis) {
            this.jedis = jedis;
        }
    
        /**
         * 判断行为是否被允许
         *
         * @param userId        用户id
         * @param actionKey     行为key
         * @param period        限流周期
         * @param maxCount      最大请求次数(滑动窗口大小)
         * @return
         */
        public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
            String key = this.key(userId, actionKey);
            long ts = System.currentTimeMillis();
            Pipeline pipe = jedis.pipelined();
            pipe.multi();
    
            // 每个用户一个 zset,这里是 user + key 组合
            pipe.zadd(key, ts, String.valueOf(ts));
            // 移除滑动窗口之外的数据
            pipe.zremrangeByScore(key, 0, ts - (period * 1000));
            // 获取窗口内的数量
            Response<Long> count = pipe.zcard(key);
            // 设置行为的过期时间,如果数据为冷数据,zset将会删除以此节省内存空间(不是必须,算是优化)
            pipe.expire(key, period);
            pipe.exec();
            pipe.close();
            return count.get() <= maxCount;
        }
    
    
        /**
         * 限流key
         *
         * @param userId
         * @param actionKey
         * @return
         */
        public String key(String userId, String actionKey) {
            return String.format("limit:%s:%s", userId, actionKey);
        }
    
    }
    
    public class FirstTool {
    
        static JedisPool pool = null;
    
        static {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(8);
            config.setMaxTotal(18);
            pool = new JedisPool(config, "127.0.0.1", 6379, 2000);
        }
    
        public static void main(String[] args) throws Exception {
            Jedis jedis = pool.getResource();
    
            SimpleSlidingWindowByZSet slidingWindow = new SimpleSlidingWindowByZSet(jedis);
            for (int i = 1; i <= 15; i++) {
                boolean actionAllowed = slidingWindow.isActionAllowed("liziba", "view", 60, 5);
    
                System.out.println("第" + i +"次操作" + (actionAllowed ? "成功" : "失败"));
                TimeUnit.MILLISECONDS.sleep(1);
            }
    
    
            jedis.close();
        }
    }
    

    相关文章

      网友评论

        本文标题:Redis 实现滑动窗口

        本文链接:https://www.haomeiwen.com/subject/npvvvrtx.html