当前位置: 首页 > article >正文

Springboot(五十八)SpringBoot3使用Redisson实现接口的限流功能

这部分我记录一下我使用redission实现接口限流的全过程。

关于自定义注解,请移步《SpringBoot(二十六)SpringBoot自定义注解》

一:redission自定义限流注解主要流程

对接口实现限流,主要使用了Redisson提供的限流API方法;使用很简单:

第一步:声明一个限流器;

 RRateLimiter rRateLimiter = redissonClient.getRateLimiter(rateLimiterKey);

第二步:设置速率;举例:5秒中产生2个令牌

rRateLimiter.trySetRate(RateType.OVERALL, 2, 5, RateIntervalUnit.SECONDS);

第三步:试图获取一个令牌,获取到,返回true;否则,返回false

rateLimiter.tryAcquire();

二:自定义限流注解接口

MyRateLimiter.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

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

package com.modules.customannotations.enums;
 
public enum LimitType {
    /**
     * 默认限流策略,针对某一个接口进行限流
     */
    DEFAULT,
 
    CLUSTER,
    /**
     * 根据IP地址进行限流
     */
    IP;
}

五:测试一下

通过使用@RateLimiter 注解配置:count = 2, time = 10;即:每10秒钟产生2个令牌。

   
 @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

控制台输出:

1111111111.png

使用redission实现自定义限流注解成功。

有好的建议,请在下方输入你的评论。


http://www.kler.cn/a/507456.html

相关文章:

  • 【 MySQL 学习2】常用命令
  • Oracle 批量投入数据方法总结
  • 深度学习图像算法中的网络架构:Backbone、Neck 和 Head 详解
  • Android BottomNavigationView不加icon使text垂直居中,完美解决。
  • 4G DTU赋能智能配电环网柜通信运维管理
  • LabVIEW智能水肥一体灌溉控制系统
  • .Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)
  • Spring boot学习文档
  • Linux 内核自旋锁spinlock(一)
  • 基于单片机的智能火灾报警系统设计
  • 远程桌面使用是TCP还是UDP?
  • LLM - 大模型 ScallingLaws 的迁移学习与混合训练(PLM) 教程(3)
  • Mysql常见问题处理集锦
  • PTA L1-039 古风排版
  • Bootstrap 下拉菜单
  • 【Redis】Redis大key的危害及解决方案分享
  • WordPress内容保护策略:如何反击并利用被盗内容
  • MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 基础篇 part 5
  • NVIDIA发布个人超算利器project digital,标志着ai元年的开启
  • BERT的中文问答系统64
  • docekr在 x86d的 环境下打包arm64架构的docker包
  • RK3568平台(音频篇)lineout无声调试
  • 游程编码RLE的简单解释
  • opencv基础学习
  • 画流程图 代码生成流程图 流程图自动运行
  • JavaScript系列(31)--装饰器详解