Guava RateLimiter实现接口API限流

邮差的信 提交于 2019-11-26 20:35:24

一、简介

Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率。RateLimit二的原理类似与令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒1/permitsPerSecond的速率释放许可。

使用RateLimiter需要引入的jar包:

<!-- Guava是一种基于开源的Java库,谷歌很多项目使用它的很多核心库。这个库是为了方便编码,并减少编码错误 -->
<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>${guava.version}</version>
</dependency>

 

注意:

Guava 20(将于2016年初发布)将是支持Java 6甚至Java 7的最终Guava版本.Guava 21(理想情况下是2016年中期)将需要Java 8。

在版本21中,我们还将启动一个新的分支,可能称为guava-android。它将保持Java 6兼容性,允许它用于支持最小版本Gingerbread的Android应用程序。

 方法摘要:

参考地址:https://www.cnblogs.com/exceptioneye/p/4824394.html

返回类型 方法和描述
static RateLimiter

create(double permitsPerSecond)

根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)

static RateLimiter

create(double permitsPerSecond,Long warmupPeriod,TimeUnti unit)

根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少请求量),在这段预热时间内,RateLimiter每秒分配许可数会平稳的增长直到预热期结束是达到其最大速率。(只要存在足够请求数来时其饱和)

double

acquire()

从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求。

double

acquire(int permits)

从RateLimiter获取指定数量许可,该方法会被阻塞直到获取到请求。

double

getRate()

返回RateLimiter配置中的稳定速率,该速率单位是每秒多少许可数

void

setRate(double pemitsPerSecond)

更新RateLimiter的稳定速率,参数permitsPerSecond由构造RateLimiter的工厂方法提供。

boolean

tryAcquire()

从RateLimiter获取许可,如果该许可可以在无延迟下的情况下立即获取的话返回true,否则返回false。

boolean

tryAcquire(int permits)

从RateLimiter中获取指定数量许可,如果该 许可数可以在无延迟的情况下立即获取得到返回true,否则返回false

boolean

tryAcquire(int permits,long timeout,TimeUnit unit)

从RateLimiter中获取指定数量许可,如果该许可可以在不超过timeout的时间内获取得到的话返回true,如果无法在timeout时间内获取到许可则返回false。

简述:在指定时间(timeout)内获取指定数量(permits)许可。

boolean

tryAcquire(long timeout,TimeUnit unit)

从RateLimiter中获取一个许可,如果该许可可以在不超过timeout的时间内获取得到的话返回true,如果无法在timeout时间内获取到许可则返回false

简述:在指定时间内获取一个许可。

二、Demo

@Test
public void  rateLimit(){
    String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    // 这里的1表示每秒允许处理的量为1个
    RateLimiter limiter = RateLimiter.create(1.0);
    for (int i = 1; i <= 10; i++) {
        // 请求RateLimiter, 超过permits会被阻塞
        limiter.acquire();
        System.out.println("count => " + 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.标识注解:

注解:
import java.lang.annotation.*;

/**
 * 自定义限流注解(标识注解)
 *
 * @Target:
 *          表示该注解可以用于什么地方,可能的ElementType参数有:
 *          CONSTRUCTOR:构造器的声明
 *          FIELD:域声明(包括enum实例)
 *          LOCAL_VARIABLE:局部变量声明
 *          METHOD:方法声明
 *          PACKAGE:包声明
 *          PARAMETER:参数声明
 *          TYPE:类、接口(包括注解类型)或enum声明
 *
 * @Retention
 *          表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
 *          SOURCE:注解将被编译器丢弃
 *          CLASS:注解在class文件中可用,但会被VM丢弃
 *          RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息
 *
 * @Document
 *          将注解包含在Javadoc中
 *
 * @Inherited
 *          允许子类继承父类中的注解
 *
 * @Date: 2019-04-09 16:31
 */
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limiting {
}
 切面:
import com.alibaba.fastjson.JSON;
import com.github.aspect.util.ResponseUtil;
import com.github.enums.http.HttpStatusEnum;
import com.github.vo.util.ResultUtil;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

/**
 * @Date: 2019-04-09 17:06
 */
@Slf4j
@Aspect
@Component
public class LimitingAspect {

    @Autowired
    public HttpServletResponse response;

    /**
     * 比如说,我这里设置"并发数"为5
     */
    private RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**
     * 以注解为切入点
     */
    @Pointcut("@annotation(com.github.annotation.Limiting)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        // 获取许可。如果有返回true,否则返回false
        Boolean flag = rateLimiter.tryAcquire();
        Object obj = null;
        try {
            if (flag) {
                obj = joinPoint.proceed();
            } else {
                // 失败返回客户端的信息,例如:{"code":500,"msg":"服务器繁忙,请稍后再试!"}
                String result = JSON.toJSONString(ResultUtil.fail(HttpStatusEnum.BUSY_SERVER));
                ResponseUtil.output(response, result);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return obj;
    }
}
使用:
import com.github.annotation.Limiting;
import com.github.annotation.RateLimit;
import com.github.vo.Result;
import com.github.vo.util.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 测试RateLimit限流(自定义注解+切面)
 * @Date: 2019-04-04 14:17
 */
@Slf4j
@Controller
public class AppController {

    /**
     * AtomicInteger是一个线程安全的具有原子性的能返回int类型的对象
     */
    private static AtomicInteger num = new AtomicInteger(0);

    @Limiting
    @GetMapping("app")
    @ResponseBody
    public Result app(){
        // num.incrementAndGet()表示先对自身进行+1操作,再返回
        log.info("app()  i = {}",num.incrementAndGet());
        return ResultUtil.success();
    }
}
测压结果:

2.带参注解

注解:
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 *
 * 可控的限流注解
 * 相对@Limiting来说高级一点
 *
 * @Date: 2019-04-10 10:35
 */
@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;

}
 切面:
import com.alibaba.fastjson.JSON;
import com.github.annotation.RateLimit;
import com.github.aspect.util.ResponseUtil;
import com.github.enums.http.HttpStatusEnum;
import com.github.vo.util.ResultUtil;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @Date: 2019-04-10 10:40
 */
@Slf4j
@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    private HttpServletResponse response;

    private RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE);

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

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("拦截到了{}方法...", joinPoint.getSignature().getName());
        Object obj = null;
        // 获取目标方法
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Method targetMethod = methodSignature.getMethod();

        if (targetMethod.isAnnotationPresent(RateLimit.class)) {
            // 获取目标方法的@LxRateLimit注解
            RateLimit rateLimit = targetMethod.getAnnotation(RateLimit.class);
            rateLimiter.setRate(rateLimit.perSecond());
            // rateLimit.timeOut() = 1000
            // rateLimit.timeOutUnit() = TimeUnit.MILLISECONDS 毫秒
            // 判断能否在1秒内得到令牌,如果不能则立即返回false,不会阻塞程序
            if (!rateLimiter.tryAcquire(rateLimit.timeOut(), rateLimit.timeOutUnit())) {
                log.info("=====    接口并发量过大    =====");
                ResponseUtil.output(response, JSON.toJSONString(ResultUtil.fail(HttpStatusEnum.BUSY_SERVER)));
            }else{
                obj = joinPoint.proceed();
            }
        }
        return obj;
    }
}
使用:
import com.github.annotation.Limiting;
import com.github.annotation.RateLimit;
import com.github.vo.Result;
import com.github.vo.util.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 测试RateLimit限流(自定义注解+切面)
 *
 * @Date: 2019-04-04 14:17
 */
@Slf4j
@Controller
public class AppController {

    /**
     * AtomicInteger是一个线程安全的具有原子性的能返回int类型的对象
     */
    private static AtomicInteger num = new AtomicInteger(0);

    /**
     * perSecond = 0.5:每秒创建0.5个
     * timeOut = 1:在一秒的时间内如果有许可则成功,没有则失败
     *
     *
     * @return
     */
    @RateLimit(perSecond = 0.5,timeOut = 1)
    @GetMapping("apps")
    @ResponseBody
    public Result apps(){
        log.info("app()  num = {}",num.incrementAndGet());
        return ResultUtil.success();
    }
}
测压结果:

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!