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