限流尽可能在满足需求的情况下越简单越好!
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();
}
}