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

embedded/2025/1/17 20:10:30/

这部分我记录一下我使用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.24number = 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]);}// 解析返回给keytry{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){// 获取请求ipstringBuffer.append(request.getRemoteAddr() + ":");}else if (rateLimiter.limitType() == LimitType.CLUSTER){// 获取客户端实例id 3.24stringBuffer.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

控制台输出:

1111111111.png

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

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


http://www.ppmy.cn/embedded/154752.html

相关文章

基于微信小程序的驾校预约管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

QT + Opencv 实现灰度模板匹配

QT Opencv 实现灰度模板匹配 实现思路 1.模板创建代码思路 1 初始化和准备&#xff1a; 使用 cv::buildPyramid 函数构建图像金字塔。图像金字塔是一种多分辨率表示&#xff0c;每个层级的图像分辨率逐步降低。 调整 m_TemplData 的大小以匹配图像金字塔的层级数。 计算每…

idea无法下载源码

1. 方式一 在项目下&#xff0c;项目根目录下 或 pom.xml同级目录中执行 mvn dependency:resolve -Dclassifiersources然后点击“download source”时就能看到源码了。

QT跨平台应用程序开发框架(3)—— 信号和槽

目录 一&#xff0c;基本概念 二&#xff0c;connect函数使用 2.1 connect 2.2 Qt内置信号和槽 2.3 一些细节 三&#xff0c;自定义信号和槽 3.1 自定义槽函数 3.2 自定义信号 3.3 带参数的信号槽 四&#xff0c;信号和槽的意义 五&#xff0c;信号和槽断开连接 六&…

【Hive】海量数据存储利器之Hive库原理初探

文章目录 一、背景二、数据仓库2.1 数据仓库概念2.2 数据仓库分层架构2.2.1 数仓分层思想和标准2.2.2 阿里巴巴数仓3层架构2.2.3 ETL和ELT2.2.4 为什么要分层 2.3 数据仓库特征2.3.1 面向主题性2.3.2 集成性2.3.3 非易失性2.3.4 时变性 三、hive库3.1 hive概述3.2 hive架构3.2.…

【机器学习:二十、拆分原始训练集】

1. 如何改进模型 模型的改进需求 在机器学习任务中&#xff0c;模型性能的提升通常受限于训练数据、模型架构、优化方法及超参数设置等。模型改进的目标是在测试数据上表现更优&#xff0c;避免过拟合或欠拟合。 常见的改进方向 增大训练数据集&#xff1a;通过数据增强或获…

.Net MVC中视图的View()的具体用法

在控制器中我们执行完逻辑之后&#xff0c;然后就是要准备开始跳转到视图中&#xff0c;那么该如何指定跳转的视图呢&#xff1f; public IActionResult Index() {return View(); } 如果View中参数&#xff0c;他默认寻找的视图路径是/Views/控制器名/方法名 如果找不到&#x…

DNS介绍(3):应用场景

文章目录 一、基础网络访问二、网络诊断与测试三、绕过网络限制四、安全数据传输五、智能DNS应用六、物联网与云计算中的应用 DNS&#xff08;Domain Name System&#xff0c;域名系统&#xff09;的应用场景非常广泛&#xff0c;它不仅在互联网的基础架构中扮演着关键角色&…