通过 guava 的 RateLimiter 以及 Aop 实现对 api 的限流控制

概述

借助 guava 的 RateLimiter 以及 Aop 实现对 api 的限流控制。

实现步骤如下

  1. 定义注解
  2. 在接口上使用注解
  3. 通过 aspect 解析注解并实现接口限流控制

maven 依赖

1
2
3
4
5
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>

定义接口注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtRateLimiter {

long DEFAULT_TIMEOUT = 500L;
double DEFAULT_qps = 10.0;

/**
* qps 大小,平均每秒请求次数
*
* @return double
*/
double permitsPerSecond() default DEFAULT_qps;

/**
* 单位 毫秒
*
* @return long
*/
long timeOut() default DEFAULT_TIMEOUT;
}

通过 aop 读取注解配置并执行限流控制

  • 通过 rateLimiterCache 来缓存 接口路径 和 对应的限流器 RateLimiter,避免重复创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import com.ckjava.kqc.kone.annotation.ExtRateLimiter;
import com.ckjava.kqc.kone.config.constant.ErrorCodeConstant;
import com.ckjava.kqc.kone.utils.KoneException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class RateLimiterAspect {

/**
* 存放接口的缓存
*/
private static final Cache<String, RateLimiter> rateLimiterCache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();

@Pointcut("@annotation(extRateLimiter)")
public void extRateLimiterPointcut(final ExtRateLimiter extRateLimiter) {
}

@Around(value = "extRateLimiterPointcut(extRateLimiter)", argNames = "proceedingJoinPoint,extRateLimiter")
public Object doBefore(final ProceedingJoinPoint proceedingJoinPoint,
final ExtRateLimiter extRateLimiter) throws Throwable {

// 获取 servlet 请求
final ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
final HttpServletRequest request = attributes.getRequest();

// 获取配置的速率
final double permitsPerSecond = extRateLimiter.permitsPerSecond();
// 获取等待令牌等待时间
final long timeOut = extRateLimiter.timeOut();
final RateLimiter rateLimiter = getRateLimiter(request.getRequestURI(), permitsPerSecond);
final boolean acquire = rateLimiter.tryAcquire(timeOut, TimeUnit.MILLISECONDS);
if (acquire) {
return proceedingJoinPoint.proceed();
} else {
throw new KoneException(String.format("接口请求超过最大的限流:%s", permitsPerSecond)).setErrorCode(
ErrorCodeConstant.EXCEED_MAX_QPS);
}
}

private RateLimiter getRateLimiter(final String requestURI, final double permitsPerSecond) {
// 获取当前URL
RateLimiter rateLimiter = rateLimiterCache.getIfPresent(requestURI);
if (ObjectUtils.isEmpty(rateLimiter)) {
rateLimiter = RateLimiter.create(permitsPerSecond);
rateLimiterCache.put(requestURI, rateLimiter);
}
return rateLimiter;
}
}

在接口上使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Api("工单分类控制层")
@RestController
@RequestMapping(ApiPatterns.PREFIX + "/workorder/type")
public class OrderTypeController {

@Autowired
private OrderTypeMapper mOrderTypeMapper;

/**
* 保存
* @return KoneWorkOrder
*/
@ApiOperation(value = "获取工单分类")
@KoneResponseResult("获取工单分类成功!")
@GetMapping("list")
@ExtRateLimiter(permitsPerSecond = 3.0, timeOut = 500)
public List<KoneOrderType> list(@RequestParam final Integer type) {
final KoneOrderType orderType = KoneOrderType.builder().type(type).build();
return mOrderTypeMapper.list(orderType);
}
}

参考

打赏

  • 微信

  • 支付宝