Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法实现流量限制
第一步:引入guava依赖包
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
第二步:给接口加上限流逻辑
@RestController
@RequestMapping("/test2")
public class AopTextController {
/**
* 限流策略 : 1秒钟2个请求
*/
private final RateLimiter limiter = RateLimiter.create(2.0);
@RequestMapping("/limit1")
public String limit1(){
//500毫秒内,没拿到令牌,就直接进入服务降级
boolean bool = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);
if(!bool){
System.out.println("系统繁忙,请稍后再试.");
return "系统繁忙,请稍后再试.";
}
System.out.println("limit1请求成功!!!");
return "请求成功";
}
}
输出结果:
真是项目中不这样用,每个接口都需要手动给其加上tryAcquire(),业务代码和限流代码混在一起,而且明显违背了DRY原则。
所以优化方式:自定义注解+AOP实现接口限流。
第一步:引入AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:自定义限流注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CacheableTest {
/**
* 资源的key,唯一
* 作用:不同的接口,不同的流量控制
*/
String key();
/**
* 一秒最多的访问限制次数
*/
double permitsPerSecond () ;
/**
* 获取令牌最大等待时间,单位毫秒
*/
long timeout();
/**
* 得不到令牌的提示语
*/
String msg() default "系统繁忙,请稍后再试.";
}
第三步:使用AOP切面拦截限流注解
@Component
@Aspect
public class TestAop {
/**
* 不同的接口,不同的流量控制
* map的key为 Limiter.key
* RateLimiter 限流策略 : 1秒钟n个请求
*/
private final Map<String, RateLimiter> limitMap = new HashMap<>();
@Pointcut("@annotation(com.wang.aop.CacheableTest)") //第一个星号指返回值类型为任意
private void pointCut(){};
@Around(value = "pointCut()")
public Object logBefore(JoinPoint joinpoint) throws Throwable {
//获取注解对象
CacheableTest cacheableTest = ((MethodSignature)joinpoint.getSignature()).getMethod().getAnnotation(CacheableTest.class);
if (cacheableTest != null){
RateLimiter rateLimiter = limitMap.get(cacheableTest.key());
if (rateLimiter == null){
rateLimiter = RateLimiter.create(cacheableTest.permitsPerSecond()); //限流策略 : 1秒钟2个请求
limitMap.put(cacheableTest.key(), rateLimiter);
}
boolean bool = rateLimiter.tryAcquire(cacheableTest.timeout(), TimeUnit.MILLISECONDS); //xx毫秒内,没拿到令牌,就直接进入服务降级
if(!bool){
//被限流
System.out.println(cacheableTest.msg());
return null;
}
}
ProceedingJoinPoint point = (ProceedingJoinPoint) joinpoint;
return point.proceed();
}
}
第四步:调用接口方法:
@RestController
@RequestMapping("/test2")
public class AopTextController {
@RequestMapping("/limit1")
@CacheableTest(key="limit1", permitsPerSecond=2.0, timeout=500)
public String limit1(){
System.out.println("请求成功");
return "请求成功";
}
}
打印结果: