springBoot使用guava的令牌桶机制实现限流

      guava是谷歌提供的一RateLimiter,指定一个qps的值,请求来需要acquire获取令牌,直到令牌重新填充才得到放行。tryAcquire方法的话,可以指定一个等待时间,并返回一个Boolea值。套框架,我们这里需要用到的是它的限流器:不足之处:所有的请求进来都是调用acquire。无法根据ip或者其他的类型关键字来区分。所以我们引入了缓存,类似HashMap,针对不同的关键字(本文使用方法名)生成不同的限流器。

1、引入配置依赖

低版本没有RateLimiter方法,版本不对时可升一下版本号

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

2、注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
    int NOT_LIMITED = 0;

    @AliasFor("qps")
    double value() default NOT_LIMITED;


    @AliasFor("value")
    double qps() default NOT_LIMITED;

    /**
     * 超时时长
     */
    int timeout() default 0;


    /**
     * 超时时间单位
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

3、切面

@Slf4j
@Aspect
@Component
public class RateLimitAspect {
    private static final ConcurrentMap<String, RateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>(16);
    @Pointcut("@annotation(com.example.springbootdemoratelimitguava.annotation.RateLimit)")
    public void rateLimit() {

    }
    @Around("rateLimit()")
    public Object pointcut(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RateLimit rateLimit = AnnotationUtils.findAnnotation(method, RateLimit.class);
        if (Objects.nonNull(rateLimit) && rateLimit.qps() > RateLimit.NOT_LIMITED) {
            double qps = rateLimit.qps();
            // 缓存池中是否有对象 没有则初始化刷入
            if (Objects.isNull(RATE_LIMITER_CACHE.get(method.getName()))) {
                RATE_LIMITER_CACHE.put(method.getName(), RateLimiter.create(qps));
            }
            log.debug("[{}]的qps设置为:{}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate());
            // 尝试获取令牌
            if (Objects.nonNull(RATE_LIMITER_CACHE.get(method.getName())) && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimit.timeout(), rateLimit.timeUnit())) {
                throw new RuntimeException("请求次数过多,请稍后再试");
            }
        }
        return proceedingJoinPoint.proceed();
    }
}

4、全局异常捕捉

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(RuntimeException.class)
    public Dict handler(RuntimeException exception) {
        return Dict.create().set("msg", exception.getMessage());
    }
}

5、测试方法

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @RateLimit(value = 1.0, timeout = 300)
    @GetMapping("/test1")
    public Dict test1() {
        log.info("【test1】被执行了。。。。。");
        return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~");
    }

    @GetMapping("/test2")
    public Dict test2() {
        log.info("【test2】被执行了。。。。。");
        return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃");
    }

    @RateLimit(value = 2.0, timeout = 300)
    @GetMapping("/test3")
    public Dict test3() {
        log.info("【test3】被执行了。。。。。");
        return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~");
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值