需求背景: 已知第三方接口限流策略,比如60次/min,那在内部调用第三方接口时,为避免过多无效请求,造成ip被封的风险,故需引入限流器,此处采用Redisson中RRateLimiter限流器;
Redisson导入依赖,注入bean在上篇文章有写,此处不在重复;
https://blog.csdn.net/qq_34373761/article/details/128156004
1、注解类创建
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RedissonRateLimiterRequest {
@ApiModelProperty("限流的key拼接 限制3个参数")
String keyPattern();
String key1();
String key2();
String key3();
@ApiModelProperty("限流模式,默认单机")
RateType type() default RateType.PER_CLIENT;
@ApiModelProperty("限流速率,默认每秒20")
long rate() default 20;
@ApiModelProperty("速率间隔")
long rateInterval() default 1;
@ApiModelProperty("速率间隔单位: 秒")
RateIntervalUnit timeUnit() default RateIntervalUnit.SECONDS;
}
2、切面类实现
package com.wwp.util.redisson;
import com.wwp.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
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.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateLimiterConfig;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author wwp
* @Description: redisson限流请求切面
* @date 2022/12/3 15:24
*/
@Slf4j
@Aspect
@Component
public class RedissonRateLimitRequestAspect {
@Autowired
private RedissonClient redisson;
/**
* 根据自定义注解获取切点
*
* @param requestRateLimiter
*/
@Pointcut("@annotation(requestRateLimiter)")
public void accessLimit(RedissonRateLimiterRequest requestRateLimiter) {
}
@Around(value = "accessLimit(requestRateLimiter)", argNames = "pjp,requestRateLimiter")
public Object around(ProceedingJoinPoint pjp, RedissonRateLimiterRequest requestRateLimiter) throws Throwable {
// 预处理 参数,将占位符转为实际值,此处需求是3个维度的值,所有有三个key
Object value1 = AnnotationResolver.newInstance().resolver(pjp, requestRateLimiter.key1());
Object value2 = AnnotationResolver.newInstance().resolver(pjp, requestRateLimiter.key2());
Object value3 = AnnotationResolver.newInstance().resolver(pjp, requestRateLimiter.key3());
// 限流拦截器
RRateLimiter rRateLimiter = this.getRateLimiter(requestRateLimiter, value1, value2, value3);
// 堵塞 需求是限制请求次数,过多请求直接堵塞等待,因为对内接口,请求数可控,否则可用tryAcquire
rRateLimiter.acquire();
return pjp.proceed();
}
/**
* 获取限流拦截器
*
* @param requestRateLimiter
* @return
*/
public RRateLimiter getRateLimiter(RedissonRateLimiterRequest requestRateLimiter, Object... keys) {
// 解析全部参数
String key = String.format(requestRateLimiter.keyPattern(), keys);
RRateLimiter rRateLimiter = redisson.getRateLimiter(StringUtil.isBlank(key) ? "rs:limiter" : key);
// 设置限流
if (rRateLimiter.isExists()) {
RateLimiterConfig rateLimiterConfig = rRateLimiter.getConfig();
// 更新参数,重新加载限流器配置
if (!Objects.equals(requestRateLimiter.rate(), rateLimiterConfig.getRate())
|| !Objects.equals(requestRateLimiter.timeUnit().toMillis(requestRateLimiter.rateInterval()), rateLimiterConfig.getRateInterval())
|| !Objects.equals(requestRateLimiter.type(), rateLimiterConfig.getRateType())) {
rRateLimiter.delete();
rRateLimiter.trySetRate(requestRateLimiter.type(), requestRateLimiter.rate(), requestRateLimiter.rateInterval(), requestRateLimiter.timeUnit());
}
// 新增限流规则
} else {
rRateLimiter.trySetRate(requestRateLimiter.type(), requestRateLimiter.rate(), requestRateLimiter.rateInterval(), requestRateLimiter.timeUnit());
}
return rRateLimiter;
}
/**
* 解析注解参数,语法 #{XXX}
* 支持#{user}、#{user.id}、#{user.account.id}
*/
static class AnnotationResolver {
private final static AnnotationResolver resolver = new AnnotationResolver();
public static AnnotationResolver newInstance() {
return resolver;
}
/**
* 解析注解上的值
*
* @param joinPoint
* @param str 需要解析的字符串
* @return
*/
public Object resolver(JoinPoint joinPoint, String str) {
if (str == null) return null;
Object value = null;
// 如果str匹配上了#{},则为动态变量
if (str.matches("#\\{\\D*\\}")) {
String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
// 复杂类型
if (newStr.contains(".")) {
try {
value = this.complexResolver(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = this.simpleResolver(joinPoint, newStr);
}
//非变量
} else {
value = str;
}
return value;
}
private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strArr = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strArr[0].equals(names[i])) {
Object obj = args[i];
Method method = obj.getClass().getDeclaredMethod(getMethodName(strArr[1]), null);
Object value = method.invoke(args[i]);
return getValue(value, 1, strArr);
}
}
return null;
}
private Object getValue(Object obj, int index, String[] strArr) {
try {
if (obj != null && index < strs.length - 1) {
Method method = obj.getClass().getDeclaredMethod(getMethodName(strArr[index + 1]), null);
obj = method.invoke(obj);
getValue(obj, index + 1, strArr);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getMethodName(String name) {
return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
}
private Object simpleResolver(JoinPoint joinPoint, String str) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
if (str.equals(names[i])) {
return args[i];
}
}
return null;
}
}
}
3、实际应用
/**
* 限流 15次/1s
*
* @param authShopId
* @param cateId
* @param tryNum
* @return
* @throws MpException
* key1:应用id key2:平台id key3:接口名称
*/
@RedissonRateLimiterRequest(keyPattern = "rate_limit_key:%s:%s:%s", key1 = "1", key2 = "32", key3 = "catQry", rate = 15)
private List<CatsGetResponse.CategoryDTOListItem> getShopCategory(Integer authShopId, Long cateId, Integer tryNum) throws Exception {
logger.info("查询分类");
try {
GoodsCatsGetRequest request = new GoodsCatsGetRequest();
//固定传1
request.setSiteId(1L);
request.setParentCatId(cateId);
// 第三方接口
var response = this.execute(request, authShopId);
return response.getResult().getCategoryDTOList();
} catch (Exception e) {
if (Objects.isNull(tryNum) && Constant.LIMIT_CODE.equals(e.getPlatformCode())) {
try {
Thread.sleep(1000);
logger.info("调用过于频繁,等待1s");
return this.getShopCategory(authShopId, cateId, 1);
} catch (InterruptedException ie) {
logger.error("查询分类失败", ie);
Thread.currentThread().interrupt();
}
}
throw e;
}
}