Springboot--高并发状况下使用Guava RateLimiter进行限流(对不同的API接口定制限流)

限流是应对高并发的策略之一,而使用Guava的RateLimiter能够方便快捷的实现API接口访问的限流。

RateLimiter特点:

  1. 使用了令牌桶算法,也就是说规定了产生令牌的速率,以及令牌桶的容量,也就是说在指定时间内对请求的响应数量。
  2. RateLimiter 允许某次请求拿走超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上,并且桶中有足够本次请求使用的令牌为止。

使用:
首先引入依赖

 		 <dependency>
            <groupId>com.google.guavagroupId>
            <artifactId>guavaartifactId>
            <version>28.2-jreversion>
        dependency>

Limiter.java(限流注解)

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Limiter {
    double LimitNum() default  10;      //默认每秒产生10个令牌
}

LimitException.java(自定义的限流异常类)

public class LimitException extends RuntimeException{
    private Integer code;
    private String msg;


    public LimitException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
	//get和set省略
}

MyExceptionHandle.java(捕获自定义异常类,并进行处理)

@RestControllerAdvice
public class MyExceptionHandle {
    private static final Logger LOG = LoggerFactory.getLogger(MyExceptionHandle.class);

    @ExceptionHandler(LimitException.class)
    public Object Handle(Exception e, HttpServletRequest request){
        LOG.error("msg:{},url:{}", ((LimitException)e).getMsg(), request.getRequestURL());
        Map<String, Object> map = new HashMap<>();
        map.put("code",((LimitException) e).getCode());
        map.put("msg",((LimitException) e).getMsg());
        map.put("url", request.getRequestURL());
        return map;
    }
}

RateLimiterAspect.java(AOP切面,切入点是使用了上述LimitException注解的方法)

@Aspect
@Component
public class RateLimiterAspect {
	//创建一个ConcurrentHashMap来存放各个方法和它们自己对应的RateLimiter对象
    private static final ConcurrentMap<String, RateLimiter> RATE_LIMITER = new ConcurrentHashMap<>();
    @Pointcut("@annotation(com.work.ohstudy.annotation.Limiter)")
    public void rateLimit() {
    }

    private RateLimiter rateLimiter;

    @Around("rateLimit()")
    public Object pointcut(ProceedingJoinPoint point) throws Throwable {
        Object obj = null;
        //获取拦截的方法名
        Signature sig = point.getSignature();
        //获取拦截的方法名
        MethodSignature msig = (MethodSignature) sig;
        //返回被织入增加处理目标对象
        Object target = point.getTarget();
        //为了获取注解信息
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        //获取注解信息
        Limiter annotation = currentMethod.getAnnotation(Limiter.class);
        double limitNum = annotation.LimitNum(); //获取注解每秒加入桶中的token
        String functionName = msig.getName(); // 注解所在方法名区分不同的限流策略

        if(RATE_LIMITER.containsKey(functionName)){
            rateLimiter=RATE_LIMITER.get(functionName);
        }else {
            RATE_LIMITER.put(functionName,RateLimiter.create(limitNum));
            rateLimiter=RATE_LIMITER.get(functionName);
        }
        if(rateLimiter.tryAcquire()) {
            return point.proceed();
        }
        else {
           throw new LimitException(500,"请求繁忙");
        }
    }
}

TestController.java(测试的Controller)
为了让测试效果直观,所以这里每秒产生令牌的速率设置成了0.01

@RestController
public class TestController {
    @RequestMapping(value = "/test1")
    @Limiter(LimitNum = 0.01)
    public RetResult Curry(){
        System.out.println("接口1请求成功");
        return RetResponse.makeOKRsp(null);
    }

    @RequestMapping(value = "/test2")
    @Limiter(LimitNum = 0.01)
    public RetResult Harden(){
        System.out.println("接口2请求成功!!");
        return RetResponse.makeOKRsp(null);
    }
}

测试1:

在浏览器输入http://localhost:8088/test1,并且刷新几次,之后再输入http://localhost:8088/test2,然后也刷新几次。
结果:
Springboot--高并发状况下使用Guava RateLimiter进行限流(对不同的API接口定制限流)_第1张图片
由上可以看出两个接口的限流是分开的。



测试2(测试RateLimiter令牌桶的预消费令牌):
先修改test1接口的令牌产生速率为每秒生成3个。

    @RequestMapping(value = "/test1")
    @Limiter(LimitNum = 3)
    public RetResult Curry(){
        System.out.println("接口1请求成功");
        return RetResponse.makeOKRsp(null);
    }

使用Postman测试,并发数10
结果:
Springboot--高并发状况下使用Guava RateLimiter进行限流(对不同的API接口定制限流)_第2张图片
可以看到明明每秒产生的令牌是3个,可这里请求却响应了4个,这就是之前说的预消费令牌。

把令牌产生速率改为每秒10个,并发数改为20,测试结果如下:
Springboot--高并发状况下使用Guava RateLimiter进行限流(对不同的API接口定制限流)_第3张图片
可以看出预消费令牌的数量也和每秒产生令牌的速率有关。在每秒生成3个时,预消费了1个令牌,而每秒生成10个时,预消费了3个令牌。

你可能感兴趣的:(Springboot运用)