关于通过RateLimiter 和Spring Aop 实现限流

限流

当遇到某个请求量激增时,可能会倒是接口占用过多的服务器资源,使得其他请求响应时间过慢或超时,有可能导致服务器挂机。这时可能通过对请求进行限制,对于部分超时请求,快速返回失败;

限流的算法

漏桶算法

水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率

令牌桶算法

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。

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 查看本文章

猜你喜欢

转载自juejin.im/post/5eafa496f265da7be67ad334