技术交流:综合利用CacheBuilder和RateLimiter进行限流控制

综合利用CacheBuilder和RateLimiter进行限流控制

  • 需求
  • 解决方案
    • Limiter
    • RateLimiterAspect
    • 注解使用

需求

系统拦截白名单下有部分接口是公开的,便于外部查询数据,这些接口均需要传输accessKey作为访问钥匙,如下有三种适用场景:

  1. 没有传输该参数值可第一时间打回;
  2. 创建错误的accessKey校验直接打回;
  3. 传输正确的accessKey,查询账号相关信息,然后进行查询操作;

细分思路,最终将上述三种场景划分为:

  1. 没有传输该参数值可第一时间打回;
  2. 创建错误的accessKey校验直接打回;
  3. 第一次传输正确的accessKey,查询账号相关信息,然后进行查询操作(缓存该accessKey并设置过期时间,便于下次直接查询到账号相关信息);
  4. 再次传输正确的accessKey,直接从缓存中读取账号信息,刷新缓存过期时间,然后进行查询操作;

解决方案

Limiter

功能:创建Limiter接口来定义令牌数量。

示例代码:

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Limiter {

    double LimitNum() default 10;      //默认每秒产生10个令牌

}

RateLimiterAspect

功能:设置Cache RATE_LIMITER 来缓存有效的账号信息,五分钟有效,下次访问已有缓存会刷新过期时间。

示例代码:

 private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterAspect.class);

    public static final double RATE = 100;

    private static final Cache<String, RateLimiter> RATE_LIMITER = CacheBuilder
        .newBuilder()
        .maximumSize(100L)
        .expireAfterAccess(5L, TimeUnit.MINUTES)
        .build();

    @Pointcut("@annotation(xyz.ddsofcai.www.web.aop.limit.Limiter)")
    public void rateLimit() {
    }

    @Around("rateLimit()")
    public Object pointcut(ProceedingJoinPoint point) throws Throwable {
        String accessKey = getAccessKey(point);
        RateLimiter rateLimiter = RATE_LIMITER.get(accessKey, () -> {
            Limiter limiter = getAnnotation(point);
            return RateLimiter.create(limiter.LimitNum());
        });

        if (rateLimiter.tryAcquire()) {
            return point.proceed();
        }

        LOGGER.info("频繁请求限制 accessKey: {}, methodName: {}", accessKey, point.getSignature().getName());

        throw new BizException("频繁请求限制");
    }

    private Limiter getAnnotation(ProceedingJoinPoint point) throws NoSuchMethodException {
        //获取拦截的方法名
        Signature sig = point.getSignature();
        //获取拦截的方法名
        MethodSignature msig = (MethodSignature) sig;
        //返回被织入增加处理目标对象
        Object target = point.getTarget();
        //为了获取注解信息
        Method method = target.getClass()
                              .getMethod(msig.getName(), msig.getParameterTypes());
        //获取注解信息
        return method.getAnnotation(Limiter.class);
    }

    private String getAccessKey(ProceedingJoinPoint point) {
        return Arrays.stream(point.getArgs())
                     .filter(e -> e instanceof AccessKeyParam)
                     .findFirst()
                     .map(o -> ((AccessKeyParam) o).getAccessKey())
                     .orElseThrow(() -> new BizException("accessKey不能为空"));
    }

}

注解使用


 @GetMapping("/report")
 @Limiter(LimitNum = RateLimiterAspect.RATE)
 public Page<Map<String, Object>> report(ReportPageQuery pageQuery) {
     return reportService.report(pageQuery);
 }

你可能感兴趣的:(缓存,java,开发语言)