当遇到某个请求量激增时,可能会倒是接口占用过多的服务器资源,使得其他请求响应时间过慢或超时,有可能导致服务器挂机。这时可能通过对请求进行限制,对于部分超时请求,快速返回失败;
水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。
Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法(Token Bucket)来完成限流,RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率.它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到.
创建一个RateLimiter ,设置速率为每秒生成 5 个令牌, 那么桶的最大值也为 5
RateLimiter rateLimiter = RateLimiter.create(5) 复制代码
RateLimiter 提供了一些方法用于帮助判断
方法 | 作用 |
---|---|
setRate(double permitsPerSecond) | 设置每秒向桶中放入令牌的数量 |
accquire() | 获取一个令牌,阻塞直到该请求可以为止,返回花费的时间 |
accquire(int permits) | 获取指定数量的令牌, 该方法也会阻塞, 返回值为获取到这 N 个令牌花费的时间 |
tryAcquire() | 判断时候能获取到令牌, 如果不能获取立即返回 false |
tryAcquire(int permits) | 判断时候能获取到n个令牌, 如果不能获取立即返回 false |
tryAcquire(long timeout,TimeUnit unit) | 能否在指定时间内获取令牌,不能则返回false |
tryAcquire(int permits,long timeout,TimeUnit unit) | 能否在指定时间和单位内获取令牌,不能则返回false |
在pom中添加
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency> 复制代码
定义一个注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { /** * 每秒向桶放入令牌的数量 */ double perSecond() default Double.MAX_VALUE; /** * 获取令牌的等待时间 * * @return */ int timeOut() default 0; /** * 超时时间单位 * * @return */ TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS; } 复制代码
通过Aop判断是否使用注解@RateLimit和限流
@Log4j2 @Aspect @Configuration public class RateLimitAspect { private RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE); @Around("execution(public * *(..)) && @annotation(com.yuan.redis.authorization.RateLimit)") public Object interceptor(ProceedingJoinPoint pjp) throws Throwable { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); if (method.isAnnotationPresent(RateLimit.class)) { RateLimit rl = method.getAnnotation(RateLimit.class); rateLimiter.setRate(rl.perSecond()); if (!rateLimiter.tryAcquire(rl.timeOut(), rl.timeOutUnit())) { throw new ApiException("访问人数太多,请稍后再试试", ApiConstants.ERROR100600); } } return pjp.proceed(); } } 复制代码
在接口上加注解@RateLimit
@ApiOperation(value = "测试限流", notes = "测试限流") @RequestMapping(value = "/testRateLimiter.json", method = RequestMethod.POST) @ApiResponses({@ApiResponse(code = 5000001, message = "参数错误")}) @RateLimit(perSecond =100, timeOut = 100) public Result<Test> testRateLimiter() { return Result.jsonStringOk(); } 复制代码
通过jmeter 启用500线程压测,发现有部分不成功,加上@RateLimit(perSecond =100, timeOut = 100)后吞吐量降低了。