首页 > 数据库 >Springboot(五十八)SpringBoot3使用Redisson实现接口的限流功能

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

时间:2025-01-17 16:32:09浏览次数:3  
标签:Redisson Springboot springframework String 限流 org import rateLimiter

这部分我记录一下我使用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实现自定义限流注解成功。

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

标签:Redisson,Springboot,springframework,String,限流,org,import,rateLimiter
From: https://blog.csdn.net/qq_39708228/article/details/145153447

相关文章

  • Java基于SpringBoot+Vue的酒店管理系统(用户、酒店管理员、管理员)
    所需该项目可以在最下面查看联系方式,为防止迷路可以收藏文章,以防后期找不到项目介绍Java基于SpringBoot+Vue的酒店管理系统(用户、酒店管理员、管理员)系统实现截图技术栈介绍JDK版本:jdk1.8+编程语言:java框架支持:springboot数据库:mysql版本不限数据库工具......
  • Java基于SpringBoot+Vue的高校教室资源管理平台的设计与实现(四端:学生、教室、维护员、
    所需该项目可以在最下面查看联系方式,为防止迷路可以收藏文章,以防后期找不到项目介绍Java基于SpringBoot+Vue的高校教室资源管理平台的设计与实现(四端:学生、教室、维护员、管理员)系统实现截图技术栈介绍JDK版本:jdk1.8+编程语言:java框架支持:springboot数据......
  • springboot基于协同过滤算法的个性化音乐推荐系统
    文章目录详细视频演示项目介绍技术介绍功能介绍核心代码系统效果图详细视频演示文章底部名片,获取项目的完整演示视频,免费解答技术疑问项目介绍  随着数字音乐的普及和音乐平台的快速发展,用户面临着海量的音乐资源选择。然而,由于音乐品种繁多、个人喜好各异,用户......
  • 计算机毕业设计Springboot中天健身房 基于Springboot的健身房管理系统 Springboot健身
    计算机毕业设计Springboot中天健身房1lli75s3(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着人们健康意识的不断增强,健身房已成为许多人日常生活的一部分。为了提升健身房的管理效率和会员体验,信息化管理系统的引入显得尤为重......
  • 计算机毕业设计Springboot在线作业管理系统 基于Springboot的在线作业管理平台 Spring
    计算机毕业设计Springboot在线作业管理系统6u09wm4d(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着信息技术的迅猛发展,传统的教学方式正逐步向现代化、数字化转型。作业作为教学过程中的重要环节,其管理方式的改革尤为关键。传......
  • springboot基于Web的影像科智能信息挂管理系统_4a16y5e8
    收藏关注不迷路!!......
  • 2025毕设springboot 华南地区走失人员信息管理系统论文+源码
    系统程序文件列表开题报告内容研究背景华南地区,作为中国人口密集、流动性大的区域之一,近年来随着社会经济的快速发展,人员流动频繁,这也导致了走失人员事件的频发。无论是因认知障碍、精神健康问题还是意外迷路,每一例走失事件都牵动着无数家庭的心。然而,传统的寻找走失人员的......
  • 2025毕设springboot 火锅店管理系统论文+源码
    系统程序文件列表开题报告内容研究背景在当今快节奏的社会中,餐饮行业尤其是火锅店作为深受消费者喜爱的餐饮形式之一,面临着日益激烈的市场竞争。传统的火锅店管理方式往往依赖于人工记录与操作,不仅效率低下,还容易出错,难以满足现代餐饮业对高效、准确和个性化服务的需求。随......
  • 2025毕设springboot 婚纱租赁系统论文+源码
    系统程序文件列表开题报告内容研究背景随着婚庆行业的蓬勃发展,婚纱作为婚礼中不可或缺的重要元素,其市场需求日益旺盛。然而,传统购买婚纱的方式不仅成本高昂,而且在婚礼结束后往往面临闲置或处理困难的问题。因此,婚纱租赁作为一种经济、环保且灵活的消费模式逐渐受到新人的青......
  • 2025毕设springboot 基于Android的“自律打卡”演示录像120239论文+源码
    系统程序文件列表开题报告内容研究背景在快速发展的数字化时代,智能手机已成为人们日常生活中不可或缺的一部分,特别是Android系统因其开源性和广泛的应用基础,深受用户喜爱。随着生活节奏的加快,越来越多的人开始重视时间管理和自我提升,自律打卡作为一种有效的自我激励方式,逐......