Redisson之限流器RRateLimiter应用

需求背景: 已知第三方接口限流策略,比如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;
        }
    }
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是一个基于Redisson实现的简单限流代码示例,实现了令牌桶算法: ```java public class RedissonRateLimiter { private RedissonClient redissonClient; private RRateLimiter rateLimiter; public RedissonRateLimiter(RedissonClient redissonClient, String name) { this.redissonClient = redissonClient; this.rateLimiter = redissonClient.getRateLimiter(name); } /** * 尝试获取一个令牌,如果获取不到则会等待一定时间 * * @param permits 需要获取的令牌数 * @param timeout 最长等待时间 * @param timeUnit 时间单位 * @return 是否获取到令牌 */ public boolean tryAcquire(int permits, long timeout, TimeUnit timeUnit) throws InterruptedException { return rateLimiter.tryAcquire(permits, timeout, timeUnit); } } ``` 使用方法如下: ```java // 创建RedissonClient Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redissonClient = Redisson.create(config); // 创建限流 RedissonRateLimiter rateLimiter = new RedissonRateLimiter(redissonClient, "my-rate-limiter"); // 尝试获取令牌 if (rateLimiter.tryAcquire(1, 10, TimeUnit.SECONDS)) { // 执行业务逻辑 } else { // 返回限流结果 } ``` 其中,`tryAcquire`方法尝试获取指定数量的令牌,如果获取不到则会等待一定时间,返回值表示是否获取到令牌。在上面的示例中,如果获取到令牌则会执行业务逻辑,否则会返回限流结果。可以根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LuciferWWP

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值