限流
当遇到某个请求量激增时,可能会倒是接口占用过多的服务器资源,使得其他请求响应时间过慢或超时,有可能导致服务器挂机。这时可能通过对请求进行限制,对于部分超时请求,快速返回失败;
限流的算法
漏桶算法
水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率
令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。
RateLimiter简介
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)后吞吐量降低了。
扫描二维码关注公众号,回复:
11179440 查看本文章