SpringBoot用自定义注解+AOP+RateLimiter实现限流
使用Guava的RateLimiter实现限流,每秒最大10个请求,仅适用于单体
不保证公平访问
允许先消费,后付款,就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取
但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌
所以实际上每秒能够通过的数量会比设置的permitsPerSecond大
在设置permitsPerSecond的时候应比实际估计的流量要小
限制能卖的总包子
1.注解
@Inherited
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
// 每秒发放许可数
double permitsPerSecond() default 10;
// 超时时间,即能否在指定内得到令牌,如果不能则立即返回,不进入目标方法/类
// 默认为0,即不等待,获取不到令牌立即返回
long timeout() default 0;
// 超时时间单位,默认取毫秒
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
2.切面
//@Scope默认为单例模式
@Scope
@Aspect
@Component
public class RateLimitAspect {
// com.google.common.collect.Maps 别导错包了
// 存放RateLimiter,一个url对应一个令牌桶
private Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Pointcut("@annotation(com.xxxxxx.annotation.RateLimit)")
private void pointcut() {
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
Object obj = null;
try {
// 获取注解
RateLimit rateLimit = ((MethodSignature) joinPoint.getSignature()).getMethod()
.getAnnotation(RateLimit.class);
// 获取request,然后获取请求的url,存入map中
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String url = request.getRequestURI();
// 若获取注解不为空
if (rateLimit != null) {
// 获取注解的permitsPerSecond与timeout
double permitsPerSecond = rateLimit.permitsPerSecond();
long timeout = rateLimit.timeout();
TimeUnit timeUnit = rateLimit.timeUnit();
RateLimiter rateLimiter = null;
// 判断map集合中是否有创建有创建好的令牌桶
// 若是第一次请求该url,则创建新的令牌桶
if (!limitMap.containsKey(url)) {
// 创建令牌桶
rateLimiter = RateLimiter.create(permitsPerSecond);
limitMap.put(url, rateLimiter);
System.out.println("请求======>" + url + "创建令牌桶,容量为:" + permitsPerSecond);
}
// 否则从已经保存的map中取
rateLimiter = limitMap.get(url);
// 若得到令牌
if (rateLimiter.tryAcquire(timeout, timeUnit)) {
// 开始执行目标controller
obj = joinPoint.proceed();
} else {
// 否则直接返回错误信息
obj = Results.error("请求太过频繁,请稍后重试");
System.out.println("-----请求太过频繁-----");
}
}
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}
3.Controller
@RateLimit(permitsPerSecond = 10 ,timeout = 1000 , timeUnit = TimeUnit.MILLISECONDS)
@GetMapping("/rateLimiterAnno")
public String rateLimiteranno(){
System.out.println("-----访问成功-----");
return "成功进入rateLimiteranno()";
}
4.dependency
依赖加上:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
参考:
https://segmentfault.com/a/1190000016393473
Guava的RateLimiter类相关解释:
https://www.cnblogs.com/exceptioneye/p/4824394.html