Guava提供的RateLimiter限流原理以及漏桶算法和令牌桶算法

漏桶算法、令牌桶算法思路及使用场景

在介绍RateLimiter之前我们先看下常说的令牌桶算法和漏桶算法,看下两种算法的思想和适用场景:

令牌桶算法:
在这里插入图片描述

结合上面的图,令牌桶算法就是以固定速率生成令牌放入桶中,每个请求都需要从桶中获取令牌,没有获取到令牌的请求会被阻塞限流(桶中的令牌不够的时候),当令牌消耗速度小于生成的速度时,令牌桶内就会预存这些未消耗的令牌(直到桶的上限),当有突发流量进来时,可以直接从桶中取出令牌,而不会被限流。

漏桶算法:
在这里插入图片描述

结合上面的图,漏桶算法就是将请求放入桶中,然后始终以一个固定的速率从桶中取出请求来处理,当桶中等待的请求数超过上限后(桶的容量固定),后续的请求就不再加入桶中,而是执行拒绝策略(比如降级)

适用于需要以固定速率的场景,而在多数业务场景中,我们并不需要严格的速率,并且需要有一定的应对突发流量的能力,所以会使用令牌桶算法限流。

不管是令牌桶算法还是漏桶算法都可以用延迟计算的方式来实现,延迟计算指的是不需要单独的线程来定时生成令牌或者从漏桶中定时取出请求,而是由调用限流器的线程自己去计算是否有足够的令牌以及需要sleep的时间,延迟计算的方式可以节省一个线程资源。Guava提供的RateLimiter类就是以延迟计算的方式实现限流。

RateLimiter实现原理

先看下RateLimiter类图:

在这里插入图片描述

可以看出有两层抽象,RateLimiter类本身是一个抽象类,子类SmoothRateLimiter又做了层抽象,SmoothRateLimiter有两个子类SmoothBurstySmoothWarmingUp,可以说SmoothWarmingUp是为了SmoothBursty的升级版,是为了弥补SmoothBursty的不足的(详细见后面的分析), 这里以限流核心方法acquire()为入口,自上而下的从RateLimiter类说起。

acquire()原理分析

整个限流实现过程主要分为生产令牌、获取令牌、计算阻塞时间、阻塞线程四步,既然RateLimiter做了抽象,那么说明提取了共性,在RateLimiter里的共性是阻塞线程的逻辑,所以在acquire()里将阻塞线程这个共性点提取了出来,而将具体生产令牌、获取令牌、计算阻塞时间的细节由子类SmoothRateLimiter去实现,先看下RateLimiter类的acquire()方法代码:

 @CanIgnoreReturnValue
  public double acquire() {
   
    // 获取一个令牌
    return acquire(1);
  }
  
  public double acquire(int permits) {
   
    // 预支令牌并获取需要阻塞的时间
    long microsToWait = reserve(permits);
    // 根据microsToWait来sleep线程(共性)
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }
  
  final long reserve(int permits) {
   
    checkPermits(permits);
    synchronized (mutex()) {
   
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
  }
  
  final long reserveAndGetWaitLength(int permits, long nowMicros) {
   
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    return max(momentAvailable - nowMicros, 0);
  }
  
  // 生产令牌、获取令牌、计算阻塞时间细节由子类实现
  abstract long reserveEarliestAvailable(int permits, long nowMicros);

接着看二级抽象类SmoothRateLimiterreserveEarliestAvailable(int permits, long nowMicros)的实现逻辑,我们先看下这个类的几个重要属性:

  • nextFreeTicketMicros:下一次请求被允许的时间。当令牌数不足时,需要由当前请求的线程负责延迟计算出令牌的生产数及耗时并更新这个值,即使需要等待,当前线程也不会去阻塞等待,而是提前预支令牌,而这个预支的代价会转嫁给下一个请求,这样做的目的是为了减少线程阻塞,详细见后面源码分析
  • stableIntervalMicros:每产生一个令牌需要消耗的微秒数,这个值是根据构造器传入的permitsPerSecond换算成微秒数得来(1秒=1000000微秒)
  • maxPermits:桶中允许存放的最大令牌数
  • storedPermits:桶中当前缓存的未消耗的令牌数,当令牌消耗速度小于令牌产生速度时,桶内就会开始堆积令牌,但是这个数不会大于maxPermits

通过这几个属性已经能看出reserveEarliestAvailable(int permits, long nowMicros)方法的核心逻辑,详细代码如下:

@Override
final long reserveEarliestAvailable(int<
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值