需求:单位时间内,比如1s,限制某个接口的调用次数。
RateLimiter
RateLimiter基于令牌桶算法,有一个令牌桶,单位时间内令牌会以恒定的数量(即令牌的加入速度)加入到令牌桶中,所有请求都需要获取令牌才可正常访问。当令牌桶中没有令牌可取的时候,则拒绝请求。
定义一个接口:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RateLimit { //每秒限制次数 double limit() default 1;}
定义一个切面:
@Slf4j@Component@Aspectpublic class RateLimitAspect { private static final ConcurrentMap<String, RateLimiter> rateLimiterCache = new ConcurrentHashMap<>(); @Pointcut("@annotation(com.example.demo.RateLimit)") public void rateLimit() { } @Around("rateLimit()") public Object pointcut(ProceedingJoinPoint joinPoint) throws Throwable { Object obj = null; MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); RateLimit rateLimit = AnnotationUtils.findAnnotation(method, RateLimit.class); if (rateLimit != null) { if (rateLimiterCache.get(method.getName()) == null) { rateLimiterCache.put(method.getName(), RateLimiter.create(rateLimit.limit())); } RateLimiter rateLimiter = rateLimiterCache.get(method.getName()); if (rateLimiter != null) { if (!rateLimiter.tryAcquire()) { log.info("=====完了没抢到...====="); } else { log.info("=====我抢到了!!====="); obj = joinPoint.proceed(); } } } return obj; }}
controller:
@Slf4j@RestControllerpublic class TestController { @RateLimit(limit = 5) @GetMapping("/test") public String test() { return "请求成功"; }}
Redis
利用了Redis的自增和过期时间
@Slf4j@RestControllerpublic class TestController { private int countLimit = 5; private int timeLimit = 1; @Autowired StringRedisTemplate stringRedisTemplate; @GetMapping("/test") public String test(String key) { if (stringRedisTemplate.hasKey(key)) { long count = stringRedisTemplate.boundValueOps(key).increment(1); if (count > countLimit) { log.info("请求过于繁忙"); } } else { stringRedisTemplate.opsForValue().set(key, "1", timeLimit, TimeUnit.SECONDS); } return "请求成功"; }}