基于Redis实现限流

发布于:2025-03-13 ⋅ 阅读:(25) ⋅ 点赞:(0)

限流尽可能在满足需求的情况下越简单越好!

1、基于Redsi的increment方法实现固定窗口限流

  • Redis的increment方法保证并发线程安全
  • 窗口尽可能越小越好(太大可能某一小段时间就打满请求剩下的都拿不到令牌了)
  • 这个原理其实就是用当前时间戳然后除窗口大小 在这个窗口大小的时间内 key都一样
public class RedisRateLimiter {


    private final StringRedisTemplate redisTemplate;
    // 命令前缀
    private final String key;

    private final int rate;

    private final int window;

    public RedisRateLimiter(StringRedisTemplate redisTemplate, String key, int rate,int window) {
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.rate = rate;
        Assert.isTrue(window > 0 && window <= 60,"窗口只支持分钟内");
        this.window = window;
    }

    // 检查并获取令牌
    public boolean acquire() {
        String currentKey = key + "_" + (DateUtil.currentSeconds() / window);

        Long currentCount = redisTemplate.opsForValue().increment(currentKey);

        redisTemplate.expire(currentKey, window, TimeUnit.SECONDS);

        if (currentCount > rate){
            return false;
        }
        return true;
    }


    public void acquireSleep() {
        int count = 0;
        while (!acquire()){
            ThreadUtil.sleep(1,TimeUnit.SECONDS);
            count++;
            log.info("RedisRateLimiter[{}] try acquire sleep {}",key,count);
        }
    }

    public boolean acquireSleep(int waitSecond) {
        int count = 0;
        while (!acquire()){
            if (count >= waitSecond){
                return false;
            }
            ThreadUtil.sleep(1,TimeUnit.SECONDS);
            count++;
            log.info("RedisRateLimiter[{}] try acquire sleep {}",key,count);
        }
        return true;
    }

}

使用案例:

下面这个任务是实时请求评论和子评论接口,但是两个接口每分钟不能超过100,所以我们使用限流限制10秒不超过18即可也能满足需求。

public class ScCommentRealTimeSyncTask  {
        private RedisRateLimiter rateLimiter;

        @PostConstruct
        public void init(){
            rateLimiter = new
                    RedisRateLimiter(stringRedisTemplate,KAOLA_COMMENT_RATE_KEY,16,10);
        }

        @Scheduled(fixedDelay = 3000)
        public void task(){
            // 请求接口1
            rateLimiter.acquireSleep();
            request1();
            
            //请求接口2
            rateLimiter.acquireSleep();
            request2();

        }


    }