<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReqLimiter {
/**
* 限制频率(每秒产生多少个令牌,例如:限流qps为10 -> rate = 10.0;限制5s内只能访问一次 -> rate = 0.2)
*/
double qps();
}
@Aspect
@Component
@Slf4j
@Aspect
@Component
@Slf4j
public class ReqLimitAspect {
private static final Map<String, RateLimiter> LIMITERS = new ConcurrentHashMap<>();
@Pointcut(value = "@annotation(com.test.config.ReqLimiter)")
public void pointCut() {
}
@Before("pointCut() && @annotation(reqLimit)")
public void before(JoinPoint point, ReqLimiter reqLimit) {
double rate = reqLimit.qps();
String reqUri = getReqUri();
RateLimiter rateLimiter = LIMITERS.get(reqUri);
if (rateLimiter == null) {
log.info("init req limiter of api:{}", reqUri);
rateLimiter = RateLimiter.create(rate);
LIMITERS.put(reqUri, rateLimiter);
}
boolean succ = rateLimiter.tryAcquire(1);
if (succ) {
return;
}
fallBack(reqUri);
}
private void fallBack(String reqUri) {
throw new TooManyRequestException(reqUri, "当前访问人数过多,请稍后重试!");
}
private String getReqUri() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new ClientException("request对象获取失败");
}
HttpServletRequest request = attributes.getRequest();
return request.getRequestURI();
}
}
测试:
@GetMapping("/reqUri")
@ReqLimiter(qps = 10)
public String test() {
return testService.test();
}
限流原理
另外,分布式限流参考:
//基于redis的滑动窗口限流
public Response limitFlow(){
Long currentTime = new Date().getTime();
System.out.println(currentTime);
if(redisTemplate.hasKey("limit")) {
Integer count = redisTemplate.opsForZSet().rangeByScore("limit", currentTime - intervalTime, currentTime).size(); // intervalTime是限流的时间
System.out.println(count);
if (count != null && count > 5) {
return Response.ok("每分钟最多只能访问5次");
}
}
redisTemplate.opsForZSet().add("limit",UUID.randomUUID().toString(),currentTime);
return Response.ok("访问成功");
}