欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Guava RateLimiter 实现 API 限流,为每个方法设置不同的限流

程序员文章站 2024-01-09 18:29:16
...

Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率,咋一听有点像java并发包下的Samephore,但是又不相同,RateLimiter控制的是速率,Samephore控制的是并发量。

RateLimiter的原理类似于令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可。
引入依赖:
maven

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>
  1. 简单使用
public static void main(String[] args) {
    String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个
    for (int i = 1; i <= 10; i++) { 
        limiter.acquire();// 请求RateLimiter, 超过permits会被阻塞
        System.out.println("call execute.." + i);
    }
    String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    System.out.println("start time:" + start);
    System.out.println("end time:" + end);
}
  1. 实际项目中使用

service

@Service
public class GuavaRateLimiterService {
    /*每秒控制5个许可*/
    RateLimiter rateLimiter = RateLimiter.create(5.0);
 
    /**
     * 获取令牌
     *
     * @return
     */
    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
    
}

controller

@Autowired
    private GuavaRateLimiterService rateLimiterService;
    
    @ResponseBody
    @RequestMapping("/ratelimiter")
    public Result testRateLimiter(){
        if(rateLimiterService.tryAcquire()){
            return ResultUtil.success1(1001,"成功获取许可");
        }
        return ResultUtil.success1(1002,"未获取到许可");
    }
  1. 自定义注解使用
    自定义注解
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {

    String value() default "";

    /**
     *  每秒放入桶中的令牌数,默认最大即不限流
     * @return
     */
    double perSecond() default Double.MAX_VALUE;

    /**
     * 获取令牌的等待时间  默认1
     * @return
     */
    int timeOut() default 1;

    /**
     * 超时时间单位 默认:秒
     * @return
     */
    TimeUnit timeOutUnit() default TimeUnit.SECONDS;
}

切面类

@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private HttpServletResponse response;

	//存储每个api接口的RateLimiter
    public static Map<String, RateLimiter> rateMap = new ConcurrentHashMap<String, RateLimiter>(20);


    private static String err = "{\"message\":\"接口调用并发限制\",\"code\":\"203\",\"exception\":{\"message\":\"接口调用并发限制\"}}";

    /**
     * 定义切入点
     * 两种方式可用:
     * 1.通过指定包或者指定类切入
     *
     * @Pointcut("execution(public * com.kunchi.wdt.adapter.spi.*.*(..))")
     * 2.通过指定注解切入
     * @Pointcut("@annotation(com.kunchi.wdt.adapter.annotation.RateLimit)")
     */
    @Pointcut("@annotation(com.kunchi.wdt.elephant.annotation.RateLimit)")
    public void pointcut() {

    }


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = null;
        String className = joinPoint.getTarget().getClass().getSimpleName();
        // 获取目标方法
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        String methodName = targetMethod.getName();
		//根据每个类,方法名字获取rateLimiter 
        RateLimiter rateLimiter = rateMap.get(className+"."+methodName);
        if (rateLimiter == null) {
            rateLimiter = RateLimiter.create(Double.MAX_VALUE);
            rateMap.put(className+"."+methodName, rateLimiter);
        }


        if (targetMethod.isAnnotationPresent(RateLimit.class)) {
            // 获取目标方法的@RateLimit注解
            RateLimit rateLimit = targetMethod.getAnnotation(RateLimit.class);
            rateLimiter.setRate(rateLimit.perSecond());
            if (!rateLimiter.tryAcquire(rateLimit.timeOut(), rateLimit.timeOutUnit())) {
                output(response, err);
            } else {
                obj = joinPoint.proceed();
            }
        }
        return obj;
    }


    /**
     * 错误返回http处理
     *
     * @param response
     * @param msg
     * @throws IOException
     */
    public void output(HttpServletResponse response, String msg) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            outputStream.write(msg.getBytes("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            outputStream.flush();
            outputStream.close();
        }
    }
}

/**

  • 类描述:RateLimit限流测试(基于 注解+切面 方式)
    */
    @Controller
    public class TestController {

    @ResponseBody
    @RateLimit(perSecond = 3) //可以非常方便的通过这个注解来实现限流 每秒并发为 3
    @RequestMapping("/test")
    public String test(){
    return ResultUtil.success1(1001, “success”).toString();
    }