阅读 66

Redis 实现滑动窗口

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();
    }}

2人点赞

Redis 原理与应用



作者:放开那个BUG
链接:https://www.jianshu.com/p/846534d4828f


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐