redis实现滑动窗口

pom

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
</dependency>

整合SpringBoot

package com.wx.cp.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * JedisConfig
 *
 * @author wzq
 * @date 2022-10-10
 */
@Configuration
public class JedisConfig {
    /**
     * ip地址
     */
    @Value("${spring.redis.host}")
    private String host;

    /**
     * 端口号
     */
    @Value("${spring.redis.port}")
    private int port;

    /**
     * 密码
     */
    @Value("${spring.redis.password}")
    private String password;
    /**
     * 连接超时
     */
    @Value("${spring.redis.timeout:2000}")
    private int timeout;

    /**
     * 最大连接数
     */
    @Value("${spring.redis.jedis.pool.max-active:20}")
    private int maxActive;

    /**
     * 最大空闲数
     */
    @Value("${spring.redis.jedis.pool.max-idle:8}")
    private int maxIdle;
    /**
     * 最小空闲数
     */
    @Value("${spring.redis.jedis.pool.min-idle:2}")
    private int minIdle;

    @Bean
    public JedisPool jedisPool() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxTotal(maxActive);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password,3);
        return jedisPool;
    }

}

package com.wx.cp.utils;

import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

/**
 * 简单滑动窗口Util
 *
 * @author wzq
 * @date 2022-10-10
 */
@Slf4j
@Component
public class SimpleSlidingWindowUtil {

    @Autowired
    private JedisPool jedisPool;

    /**
     * 获取Jedis资源
     */
    public Jedis getJedis() {
        return jedisPool.getResource();
    }

    /**
     * 释放Jedis连接
     */
    public void close(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**
     * 判断行为是否被允许
     *
     * @param userId   唯一标识
     * @param maxCount 最大请求次数(滑动窗口大小)
     * @return boolean
     */
    public boolean isActionAllowed(String userId, int maxCount) {
        int period = 1;
        return this.coreMethod(userId, period, maxCount);
    }

    /**
     * 判断行为是否被允许
     *
     * @param userId   唯一标识
     * @param period   限流周期 如:1秒
     * @param maxCount 最大请求次数(滑动窗口大小)
     * @return boolean
     */
    public boolean isActionAllowed(String userId, int period, int maxCount) {
        return this.coreMethod(userId, period, maxCount);
    }

    /**
     * 限流key
     *
     * @param userId    唯一标识
     * @param actionKey 行为key
     * @return String
     */
    public String getKey(String userId, String actionKey) {
        return String.format("wechat:%s:%s", userId, actionKey);
    }

    /**
     * 判断行为是否被允许-核心方法
     *
     * @param userId   唯一标识
     * @param period   限流周期 如:1秒
     * @param maxCount 最大请求次数(滑动窗口大小)
     * @return boolean
     */
    private boolean coreMethod(String userId, int period, int maxCount) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis != null) {
                // 获取当前毫秒级
                long ts = System.currentTimeMillis();
                // 获取当前时间秒级
                String actionKey = String.valueOf(ts / 1000);
                // 获取key
                String key = this.getKey(userId, actionKey);

                //一次性发送多个命令,最后一次取回所有的返回结果
                Pipeline pipe = jedis.pipelined();
                //开启事务
                pipe.multi();
                // 每个用户一个 zset,这里是 user + key 组合
                pipe.zadd(key, ts, UUID.randomUUID().toString());
                // 移除滑动窗口之外的数据
                pipe.zremrangeByScore(key, 0, ts - (period * 1000L));
                // 获取窗口内的数量
                Response<Long> count = pipe.zcard(key);
                // 设置行为的过期时间,如果数据为冷数据,zset将会删除以此节省内存空间(不是必须,算是优化)
                pipe.pexpire(key, period * 1000L);
                pipe.exec();
                pipe.close();

                log.info("简单滑动窗口Util,Jedis状态 : {}, key :{}, 状态:{} ", jedis.ping(), key, count.get() <= maxCount);
                return count.get() <= maxCount;
            }
        } catch (Exception e) {
            log.error("简单滑动窗口Util,isActionAllowed方法报错:{}", e.getMessage(), e);
        } finally {
            jedisPool.returnResource(jedis);
        }
        return false;
    }
}

测试的代码

package com.wx.cp.utils;

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

import java.io.IOException;

/**
 * wisdom-public-weixin-cp-parent
 *
 * @author wzq
 * @date 2022-10-10
 */
@Slf4j
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 isActionAllowedAll(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;
    }

    public boolean isActionAllowed(String userId, int maxCount)  {
        try {
        // 获取当前时间秒级
        String actionKey = String.valueOf(DateUtil.currentSeconds());
        // 限流周期 1秒
        int period = 1;

        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;
        } catch (Exception e) {
            log.error("简单滑动窗口Util,isActionAllowed方法报错:{}", e.getMessage(), e);
        }
        return false;
    }


    /**
     * 限流key
     *
     * @param userId
     * @param actionKey
     * @return
     */
    public String key(String userId, String actionKey) {
        return String.format("limit:%s:%s", userId, actionKey);
    }
}

package com.wx.cp.utils;

import cn.hutool.core.date.DateUtil;
import org.springframework.beans.factory.annotation.Value;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.concurrent.TimeUnit;

/**
 * redis时间滑动窗口限流
 *
 * @author wzq
 * @date 2022-10-10
 */
public class SimpleSlidingWindowUtilTest {

    @Value("${spring.redis.host}")
    private static String host;
    @Value("${spring.redis.port}")
    private static Integer port;
    @Value("${spring.redis.database}")
    private String database;
    @Value("${spring.redis.password}")
    private static String password;
    public 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, "123456",3);
    }

//        public static void main(String[] args) {
//            //new jedis对象
//            Jedis jedis = new Jedis("10.10.210.28",6379);
//            jedis.auth("123456");
//            System.out.println(jedis.ping());
//        }

//    public static void main(String[] args) {
//        for (int i = 0; i < 100; i++) {
//            long l1 = DateUtil.currentSeconds();
//            System.out.println(l1 + "=" + System.currentTimeMillis());
//        }
//
//    }

    public static void main(String[] args) throws Exception {
        Jedis jedis = pool.getResource();
        System.out.println(jedis.ping());
        SimpleSlidingWindowByZSet slidingWindow = new SimpleSlidingWindowByZSet(jedis);
        for (int i = 1; i <= 1000; i++) {
            long l = DateUtil.currentSeconds();
            boolean actionAllowed = slidingWindow.isActionAllowedAll("wechat", String.valueOf(l), 1, 5);
            if (actionAllowed){
                System.out.println("第" + i +"次操作  = " + (actionAllowed ? "成功" : "失败") + "时间戳 = "  + l);
            }
            TimeUnit.MILLISECONDS.sleep(1);
        }
        jedis.close();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值