一、简介
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(); } }
测压结果:
来源:https://www.cnblogs.com/jidanke/p/kelly.html