这部分我记录一下我使用redission实现接口限流的全过程。
关于自定义注解,请移步《SpringBoot(二十六)SpringBoot自定义注解》
一:redission自定义限流注解主要流程
对接口实现限流,主要使用了Redisson提供的限流API方法;使用很简单:
第一步:声明一个限流器;
java
RRateLimiter rRateLimiter = redissonClient.getRateLimiter(rateLimiterKey);
第二步:设置速率;举例:5秒中产生2个令牌
java
rRateLimiter.trySetRate(RateType.OVERALL, 2, 5, RateIntervalUnit.SECONDS);
第三步:试图获取一个令牌,获取到,返回true;否则,返回false
javascript
rateLimiter.tryAcquire();
二:自定义限流注解接口
MyRateLimiter.java
java
package com.modules.customannotations.myAnnotation;
import com.modules.customannotations.enums.LimitType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRateLimiter {
/**
* 限流key,支持使用Spring el表达式来动态获取方法上的参数值
*/
String rateKey() default "";
/**
* 限流时间,单位秒
*/
int time() default 60;
/**
* 限流次数
*/
int count() default 100;
/**
* 限流类型
*/
LimitType limitType() default LimitType.DEFAULT;
/**
* 提示消息
*/
String errMsg() default "接口请求过于频繁,请稍后再试!";
}
三:实现限流注解接口
MyRateLimiterAspect.java
java
package com.modules.customannotations.annotationAspect;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.modules.customannotations.enums.LimitType;
import com.modules.customannotations.myAnnotation.MyRateLimiter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class MyRateLimiterAspect {
private final RedissonClient redissonClient;
/**
* 定义spel表达式解析器
*/
private final ExpressionParser parser = new SpelExpressionParser();
/**
* 定义spel解析模版
*/
private final ParserContext parserContext = new TemplateParserContext();
/**
* 定义spel上下文对象进行解析
*/
private final EvaluationContext context = new StandardEvaluationContext();
/**
* 方法参数解析器
*/
private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, MyRateLimiter rateLimiter) {
int time = rateLimiter.time();
int count = rateLimiter.count();
String rateLimiterKey = getRateKey(rateLimiter, point);
log.error("rateLimiterKey == {}", rateLimiterKey);
try {
RateType rateType = RateType.OVERALL;
if (rateLimiter.limitType() == LimitType.CLUSTER)
{
rateType = RateType.PER_CLIENT;
}
long number = -1;
RRateLimiter rRateLimiter = redissonClient.getRateLimiter(rateLimiterKey);
rRateLimiter.trySetRate(rateType, count, time, RateIntervalUnit.SECONDS);
if (rRateLimiter.tryAcquire())
{
// 3.24
number = rRateLimiter.availablePermits();
}
if (number == -1) {
String message = rateLimiter.errMsg();
throw new RuntimeException(message);
}
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, rateLimiterKey);
} catch (Exception ex) {
throw ex;
}
}
/**
* 解析El表达式获取lockKey
* @param rateLimiter
* @param joinPoint
* @return
*/
public String getRateKey(MyRateLimiter rateLimiter, JoinPoint joinPoint)
{
String key = rateLimiter.rateKey();
// 获取方法(通过方法签名来获取)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
// 判断是否是spel格式
if (StrUtil.containsAny(key, "#"))
{
// 获取参数值
Object[] args = joinPoint.getArgs();
// 获取方法上参数的名称
String[] parameterNames = pnd.getParameterNames(method);
if (ArrayUtil.isEmpty(parameterNames))
{
throw new RuntimeException("限流key解析异常!请联系管理员!");
}
for (int i = 0; i < parameterNames.length; i++)
{
context.setVariable(parameterNames[i], args[i]);
}
// 解析返回给key
try
{
Expression expression;
if (StrUtil.startWith(key, parserContext.getExpressionPrefix()) && StrUtil.endWith(key, parserContext.getExpressionSuffix()))
{
expression = parser.parseExpression(key, parserContext);
}
else
{
expression = parser.parseExpression(key);
}
key = expression.getValue(context, String.class) + ":";
}
catch (Exception e)
{
throw new RuntimeException("限流key解析异常!请联系管理员!");
}
}
StringBuilder stringBuffer = new StringBuilder("xk-admin:rate_limit:");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
stringBuffer.append(request.getRequestURI()).append(":");
if (rateLimiter.limitType() == LimitType.IP)
{
// 获取请求ip
stringBuffer.append(request.getRemoteAddr() + ":");
}
else if (rateLimiter.limitType() == LimitType.CLUSTER)
{
// 获取客户端实例id 3.24
stringBuffer.append(redissonClient.getId()).append(":");
}
return stringBuffer.append(key).toString();
}
}
四:限流注解中使用的枚举类
LimitType.java
java
package com.modules.customannotations.enums;
public enum LimitType {
/**
* 默认限流策略,针对某一个接口进行限流
*/
DEFAULT,
CLUSTER,
/**
* 根据IP地址进行限流
*/
IP;
}
五:测试一下
通过使用@RateLimiter 注解配置:count = 2, time = 10;即:每10秒钟产生2个令牌。
java
@GetMapping("index/rateTest")
@MyRateLimiter(rateKey = "'rateTest'+ #param1+#param2", count = 2, time = 10, limitType = LimitType.DEFAULT, errMsg = "访问超过限制,请稍后再试!")
public void rateTest(String param1, String param2) {
System.out.println("开始执行业务逻辑......");
ThreadUtil.sleep(15000);
System.out.println("结束执行业务逻辑......");
}
浏览器访问:http://localhost:7001/java/index/rateTest
控制台输出:
使用redission实现自定义限流注解成功。
有好的建议,请在下方输入你的评论。