添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Redis实现滑动窗口计数

Redis实现滑动窗口计数

最近在做项目遇到一个需求,系统需要限制同一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;