SpringBoot集成Redisson实现限流(一)

1. 简介

本文主要介绍了SpringBoot集成Redisson实现限流,主要涉及到的类为Redisson
中的org.redisson.api.RRateLimiter,其实现的是令牌桶限流

2. maven依赖


<dependency>
     <groupId>org.redissongroupId>
     <artifactId>redisson-spring-boot-starterartifactId>
     <version>3.17.0version>
     <exclusions>
         <exclusion>
             <groupId>org.springframework.bootgroupId>
             <artifactId>spring-boot-starter-actuatorartifactId>
         exclusion>
     exclusions>
 dependency>

redisson相关参数配置请参考:springboot集成redisson

3. 相关代码

  • RateLimiter
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
    /**
     *
     * @return
     */
    LimitType limitType() default LimitType.DEFAULT;
    /**
     * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
     * 格式类似于  #code.id #{#code}
     */
    String key() default "";

    /**
     * 单位时间产生的令牌数,默认100
     */
    long rate() default 100;

    /**
     * 时间间隔,默认1秒
     */
    long rateInterval() default 1;

    /**
     * 拒绝请求时的提示信息
     */
    String showPromptMsg() default "服务器繁忙,请稍候再试";
}
  • RateLimiters
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiters {
    RateLimiter[] value();
}
  • LimitType
public enum LimitType {
    DEFAULT, // 全局
    IP,       // 根据客户端ip地址限制
    CLUSTER  // 对应服务器实例
}
  • RateLimiterAspect
@Slf4j
@Aspect
public class RateLimiterAspect {

    private static final String RATE_LIMIT_KEY = "ratelimiter:";

    /**
     * 定义spel表达式解析器
     */
    private final ExpressionParser parser = new SpelExpressionParser();
    /**
     * 方法参数解析器
     */
    private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
        isAllow(point, rateLimiter);
    }

    @Before("@annotation(rateLimiters)")
    public void doBefore(JoinPoint point, RateLimiters rateLimiters) {

    }

    private void isAllow(JoinPoint point, RateLimiter rateLimiter) {
        long rateInterval = rateLimiter.rateInterval();
        long rate = rateLimiter.rate();
        String combineKey = getCombineKey(rateLimiter, point);
        RateType rateType = RateType.OVERALL;
        if (rateLimiter.limitType() == LimitType.CLUSTER) {
            rateType = RateType.PER_CLIENT;
        }
        long number = RedisUtil.rateLimiter(combineKey, rateType, rate, rateInterval);
        if (number == -1) {
            String message = rateLimiter.showPromptMsg();
            throw new RuntimeException(message);
        }
        log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", rate, number, combineKey);
    }


    /**
     * 获取rateLimite对应的复合型key值
     * @param rateLimiter
     * @param point
     * @return
     */
    private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        StringBuilder stringBuffer = new StringBuilder(RATE_LIMIT_KEY);
        String key = rateLimiter.key();
        Method method = getMethod(point);
        // 获取URI,然后计算md5
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String uniqueKey = DigestUtils.md5DigestAsHex(request.getRequestURI().getBytes(StandardCharsets.UTF_8));
        stringBuffer.append(uniqueKey);
        String ip = ServletUtil.getClientIP(request);
        // 判断是否是spel格式
        if (StrUtil.containsAny(key, "#")) {
            EvaluationContext context = new MethodBasedEvaluationContext(null, method, point.getArgs(), discoverer);
            try {
                Object value = parser.parseExpression(key).getValue(context);
                if (value != null) {
                    key = value.toString();
                }
            } catch (Exception e) {
                log.error("【{}】请求,线程:【{}】,获取spel数据失败:{}", ip, Thread.currentThread().getName(), e.getMessage());
            }
        }
        if (rateLimiter.limitType() == LimitType.IP) {
            // 获取请求ip
            stringBuffer.append(":").append(ip);
        }
        // key名称
        if (!StrUtil.hasBlank(key)) {
            stringBuffer.append(":").append(key);
        }
        return stringBuffer.toString();
    }


    /**
     * 获取切面方法对象
     *
     * @param point
     * @return
     */
    private Method getMethod(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                log.error(null, e);
            }
        }
        return method;
    }
}
  • RedisUtil
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RedisUtil {

    private volatile static RedissonClient redissonClient;
    
    public static void setRedissonClient(RedissonClient redissonClient) {
        RedisUtil.redissonClient = redissonClient;
    }

    public static RedissonClient getRedissonClient() {
        return redissonClient;
    }
    
        // =========================缓存-限流器开始=============================
    
    /**
     * 令牌桶限流(整流器)
     *
     * @param key          限流key
     * @param rateType     限流类型
     * @param rate         速率
     * @param rateInterval 速率间隔
     * @return -1 表示失败
     */
    public static long rateLimiter(String key, RateType rateType, long rate, long rateInterval) {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
        if (rateLimiter.tryAcquire()) {
            return rateLimiter.availablePermits();
        } else {
            return -1L;
        }
    }

    // =========================缓存-限流器结束=============================
}

4. 验证

@RestController
public class SsoController {
    @RequestMapping("/hello")
    @ResponseBody
    @WebLog(value = "请求数据")
    @RateLimiter(limitType = LimitType.IP, rate = 10, rateInterval = 100)
    public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
        return "Hello ";
    }

    // http://127.0.0.1:8082/login
    @PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE)
    @RateLimiter(key = "#user.name", limitType = LimitType.IP, rate = 10, rateInterval = 100)
    public String login(@Valid @RequestBody User user) {
        return "Hello ";
    }
}
  • 参考资料
    Spring Boot 整合Redisson实现限流

你可能感兴趣的:(#,ratelimiter,spring,boot,redisson,ratelimiter)