利用Redis实现时间滑动窗口限流

1 篇文章 0 订阅

背景

redisson开源框架已经提供了限流的功能,但由于项目较旧,没引入redisson,为了降低风险,用spring-redis的RedisTemplate来自行实现一个限流工具类。

实现

  • 用sortSet来存储时间窗口数据,命令是ZADD,score的值为当前时间戳
  • 利用sortSet的ZREMRANGEBYSCORE命令删除时间窗口外的数据
  • 用sortSet的ZCARD来判断是否超过限流阈值

代码实现

@Component
@Slf4j
public class RateLimiter {

    @Autowired
    RedisTemplate<String, Long> redisTemplate;

    private static final String redisPrefix = "wechat-data:limiter:";

    /**
     * @param source 资源
     * @param limit 限制值
     * @param period 间隔
     * @param unit 时间单位
     * @return 是否允许
     */
    public boolean isAllow(String source, int limit, long period, TimeUnit unit){
        if (StringUtils.isEmpty(source)) {
            log.warn("参数为空??");
            return false;
        }
        String key = redisPrefix + source;
        long now = System.currentTimeMillis();
        final long ms = TimeUnit.MILLISECONDS.convert(period, unit);
        final Boolean result = redisTemplate.execute(RateLimiterScript.instance, Collections.singletonList(key), limit, now,  ms);
        return result != null && result;
    }


    static class RateLimiterScript implements RedisScript<Boolean> {
        static RateLimiterScript instance = new RateLimiterScript();
        static String shaCache;

        @Override
        public String getSha1() {
            if (shaCache != null)
                return shaCache;
            try {
                MessageDigest mdigest = MessageDigest.getInstance("SHA-1");
                byte[] s = mdigest.digest(getScriptAsString().getBytes());
                shaCache = ByteBufUtil.hexDump(s);
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
            return shaCache;
        }

        @Override
        public Class<Boolean> getResultType() {
            return Boolean.class;
        }

        @Override
        public String getScriptAsString() {
            return "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[2]) - tonumber(ARGV[3])); " +
                    "if (redis.call('ZCARD', KEYS[1]) >= tonumber(ARGV[1])) then " +
                    "return nil;" +
                    "end; " +
                    "redis.call('ZADD', KEYS[1], ARGV[2], ARGV[2]); " +
                    "redis.call('pexpire', KEYS[1], ARGV[3]); " +
                    "return true;";
        }
    }
}

说明:

  • 考虑到原子性,这里使用了lua进行操作
  • 利用RedisTemplate的execute方法执行脚本
  • redis.call(‘ZREMRANGEBYSCORE’, KEYS[1], 0, tonumber(ARGV[2]) - tonumber(ARGV[3])):删除时间窗口外的数据
  • if (redis.call(‘ZCARD’, KEYS[1]) >= tonumber(ARGV[1])) :判断是否超过限流的阈值
  • redis.call(‘ZADD’, KEYS[1], ARGV[2], ARGV[2]):向时间滑动窗口加入数据
  • redis.call(‘pexpire’, KEYS[1], ARGV[3]):设置过期时间,防止无效key堆积
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis可以通过使用有序集合(zset)来实现滑动窗口限流算法。滑动窗口限流算法是一种常见的限流算法,它通过滑动时间窗口来计数请求并进行限制。具体实现方法如下: 1. 创建一个有序集合(zset),用于存储请求的时间戳和对应的请求标识。 2. 每次收到一个请求时,将当前时间戳和请求标识作为元素添加到有序集合中。 3. 删除有序集合中时间戳小于当前时间减去限流时间窗口的元素,以保持窗口内的请求记录。 4. 统计有序集合中请求标识的数量,如果数量超过限流阈值,则拒绝该请求。 利用Redis的有序集合特性,我们可以方便地获取某个时间段内的请求数量,并且由于有序集合的自动排序特性,可以快速进行删除操作。 下面是一个示例代码,演示了如何在Redis实现滑动窗口限流: ```java import redis.clients.jedis.Jedis; import java.util.Set; public class RedisSlidingWindow { private Jedis jedis; private String key; public RedisSlidingWindow(Jedis jedis, String key) { this.jedis = jedis; this.key = key; } public boolean isActionAllowed(int windowSize, int limit) { long currentTime = System.currentTimeMillis(); long windowStart = currentTime - windowSize * 1000; // 删除窗口之外的记录 jedis.zremrangeByScore(key, 0, windowStart); // 统计窗口内的请求数量 long count = jedis.zcard(key); // 添加当前请求记录 jedis.zadd(key, currentTime, String.valueOf(currentTime)); // 设置过期时间,防止集合无限增长 jedis.expire(key, windowSize + 1); // 判断请求数是否超过限制 return count < limit; } public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); RedisSlidingWindow slidingWindow = new RedisSlidingWindow(jedis, "sliding_window"); for (int i = 1; i <= 15; i++) { boolean actionAllowed = slidingWindow.isActionAllowed(60, 5); System.out.println("第" + i + "次操作" + (actionAllowed ? "成功" : "失败")); } jedis.close(); } } ``` 以上代码使用Jedis库连接Redis,并实现了一个`RedisSlidingWindow`类,其中`isActionAllowed`方法用于判断当前请求是否允许通过。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值