获取窗口 history数量_Redis实现滑动窗口计数

62bf839b464ff290e10ab051f8f34b24.png

最近在做项目遇到一个需求,系统需要限制同一IP下参加活动的人数,超过限制人数返回错误,也就是所谓的限流,旁边的大佬提出了用滑动窗口计数来解决,记录一下具体代码实现。

实现原理:使用Redis的zset实现,三个参数key、windowInSecond、maxcount分别表示IP,滑动窗口大小和最大限制数。

getCount()方法:获取zset中的元素个数。

increment()方法:首先获取当前时间(毫秒)和窗口的左边界时间(毫秒),执行removeRangeByScore方法(将0~左边界时间间隔内的成员按照score清除),执行add方法(当前时间作为分数添加到value中),执行expire方法(设置key的失效时间)。其中multi和exec方法是添加事务。

getLastTime()方法:获取key的剩余失效时间。

access()方法:程序的入口,获取key的value中的有效访问次数,如果大于maxcount,则限流,否则调用increment方法,将窗口内的访问数加一。

@Component
@Slf4j
public class SlidingWindowCounter {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public void increment(String key, Integer windowInSecond, Integer maxcount) {
        // 当前时间
        long currentMs = System.currentTimeMillis();
        // 窗口时间
        long maxScoreMs = currentMs - windowInSecond * 1000;

        try {
            redisTemplate.multi();
            BoundZSetOperations<String, String> bound = redisTemplate.boundZSetOps(key);
            // 按分数清除过期成员
            bound.removeRangeByScore(0, maxScoreMs);
            // 添加当前时间分数
            bound.add(currentMs + "_" + Math.random(), currentMs);
            // 设置超时时间
            bound.expire(windowInSecond, TimeUnit.SECONDS);
            redisTemplate.exec();
        } catch (Exception e) {
            redisTemplate.discard();
            log.error("窗口内容错误:{}", e.getMessage());
        }
    }

    public Long getCount(String key) {
        try {
            BoundZSetOperations<String, String> bound = redisTemplate.boundZSetOps(key);
            // 按key统计集合中的有效数量
            return bound.zCard();
        } catch (Exception e) {
            log.error("获取窗口大小错误:{}", e.getMessage());
            return 0L;
        }
    }

    public Long getLastTime(String key) {
        try {
            return redisTemplate.getExpire(key);
        } catch (Exception e) {
            log.error("获取超时时间错误:{}");
            return 0L;
        }
    }

    public boolean access(String key, Integer maxcount, Integer windowInSecond) {
        Long cnt = getCount(key);
        if (cnt.compareTo(maxcount.longValue()) < 0) {
            increment(key, windowInSecond, maxcount);
            log.info("{}:{}:{},result:true", key, cnt, getLastTime(key));
            return true;
        } else {
            log.warn("{}:over {}", key, maxcount);
            return false;
        }
    }
}

待测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值