基于Redis的分布式限流器(RateLimiter)可以用来在分布式环境下现在请求方的调用频率。既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。该算法不保证公平性。除了同步接口外,还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

如何限制HTTP请求次数(分布式)

本地缓存显然不能满足,只能考虑redis当然redis中采用lua脚本也是可以实现的。今天文中的redisson中RateLimiter也是可以的

官方例子看一下 https://github.com/redisson/redisson/wiki/6.-%E5%88%86%E5%B8%83%E5%BC%8F%E5%AF%B9%E8%B1%A1

1
2
3
4
5
6
7
8
9
10
11
12
13
RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter");
// 初始化
// 最大流速 = 每1秒钟产生10个令牌
rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

CountDownLatch latch = new CountDownLatch(2);
limiter.acquire(3);
// ...

Thread t = new Thread(() -> {
limiter.acquire(2);
// ...
});

1、创建AOP切面

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@Component
@Scope
@Aspect
public class RateLimitAop {

@Autowired
private RedissonClient redissonClient;

@Pointcut("@annotation(com.data.estimated.limiter.RateLimitAspect)")
public void serviceLimit() {

}

@Around("serviceLimit()")
public Object around(ProceedingJoinPoint joinPoint) {
ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
HttpServletRequest request = servletRequestAttributes.getRequest();//获取请求
HttpServletResponse response = servletRequestAttributes.getResponse();//获取响应
StringBuffer buffer = new StringBuffer();
String url = request.getRequestURI();
buffer.append(StrUtil.replace(url,"/","_"));
buffer.append("_");
buffer.append(IPUtil.getIpAddr(request));//获取请求IP信息
RRateLimiter rRateLimiter = getRRateLimiter(joinPoint,buffer.toString());//获取配置RRateLimiter信息
boolean flag = rRateLimiter.tryAcquire();
Object obj = null;
try {
if (flag) {
obj = joinPoint.proceed();
}else{
outRateLimiterMsg(response,new JSONObject(BaseResult.LIMIT_FAIL()).toString());//响应浏览器,超频请求
}
} catch (Throwable e) {
e.printStackTrace();
outRateLimiterMsg(response,new JSONObject(BaseResult.FAIL()).toString());//响应浏览器,错误
}
return obj;
}

/**
*
* @param joinPoint
* @param limitKey
* @return
*/
private RRateLimiter getRRateLimiter(ProceedingJoinPoint joinPoint,String limitKey){
RateLimitAspect rateLimit = getRateLimitAspect(joinPoint);
long count = rateLimit.count();//限流次数
long time = rateLimit.time();//限流时间
RRateLimiter rRateLimiter = redissonClient.getRateLimiter(StrUtil.isBlank(rateLimit.key()) ? limitKey : rateLimit.key());
if(rRateLimiter.isExists()){
RateLimiterConfig rateLimiterConfig = rRateLimiter.getConfig();//读取已经存在配置
long rateInterval = rateLimiterConfig.getRateInterval();//限时时间
long rate = rateLimiterConfig.getRate();//次数
if(time != rateInterval || rate != count){
rRateLimiter.delete();//移除配置,重新加载配置
rRateLimiter.trySetRate(RateType.OVERALL, count ,time , RateIntervalUnit.SECONDS);
}
}
rRateLimiter.trySetRate(RateType.OVERALL, count ,time , RateIntervalUnit.SECONDS);
return rRateLimiter;
}

/**
* 获取脚注配置信息
* @param joinPoint
* @return
*/
private RateLimitAspect getRateLimitAspect(ProceedingJoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RateLimitAspect rateLimit = method.getAnnotation(RateLimitAspect.class);
return rateLimit;
}

/**
* 客户端返回数据
*/
private void outRateLimiterMsg(HttpServletResponse response,String message){
PrintWriter writer = null;
try {
response.reset();
response.setCharacterEncoding("utf-8");
response.setHeader("content-type", "application/json;charset=utf-8");
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Headers", "X-Requested-With");
response.addHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
writer = response.getWriter();
writer.write(message);
} catch (Exception e) {
e.printStackTrace();
}finally {
IoUtil.close(writer);
}
}
}

2、创建脚注

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

/**
* 自定义注解可以不包含属性,成为一个标识注解
*/
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAspect {
/**
* 限流唯一标示
*
* @return
*/
String key() default "";

/**
* 限流时间
* @return
*/
long time() default 5;

/**
* 限流次数
* @return
*/
long count() default 1;
}

最后一步实战操作

1
2
3
4
5
6
7
//默认无参数方法
@RateLimitAspect

//有参数
@RateLimitAspect(key = "RateLimitAspect",count = 2,time = 10)

//如果可以多个方法key均为RateLimitAspect这采用同一个限制。