限流盡可能在滿足需求的情況下越簡單越好!
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;@PostConstructpublic void init(){rateLimiter = newRedisRateLimiter(stringRedisTemplate,KAOLA_COMMENT_RATE_KEY,16,10);}@Scheduled(fixedDelay = 3000)public void task(){// 請求接口1rateLimiter.acquireSleep();request1();//請求接口2rateLimiter.acquireSleep();request2();}}