guava限流器RateLimiter源码详解

限流是保护高并发系统的三把利器之一,另外两个是缓存和降级。

常用的限流算法有滑动窗口、令牌桶和和漏桶等。Guava中的RateLimiter使用的是令牌桶,对比其他,该算法可容忍一定突发流量的速率的限流,通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

 如上图:令牌桶算法会维护⼀个令牌( token )桶,以⼀个恒定的速度往桶⾥放⼊令牌( token ):当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。

1、guava RateLimiter使用

RateLimiter只能通过设置“令牌生成速率”(permits per second)来控制流量,无法显式设置桶的容量(burst size)。桶的容量是隐式管理的,支持以下两种模式:

  1. SmoothBursty(平滑突发):只要桶内有令牌就直接放行,也就是说系统会直接面对这突发的大量请求;这种模式下,桶的最大令牌数=permitsPerSecond (见下文解析)
  2. SmoothWarmingUp(平滑预热):认为系统在闲置一段时间后可能会存在缓存大量失效的情况,把这种情况下的系统的状态称为cool冷却状态,这种情况下系统不能达到最大的性能,如果直接面对大量的请求可能存在系统崩溃的状态,所以应该存在一个预热状态,也就是逐渐增多放行的请求数量,给系统一个缓冲时间。这种模式下,桶的最大令牌数= thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros)。(见下文解析)

1.1)示例:

import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.TimeUnit;

public class RateLimiterExample {
    public static void main(String[] args) {
        // SmoothBursty:允许突发流量
        RateLimiter burstyLimiter = RateLimiter.create(1.0);
        //burstyLimiter.setRate(10.0); 重新调整速率
        for (int i = 0; i < 10; i++) {
           final int finalI = i;
           new Thread(() -> {   // 使用burstyLimiter
               burstyLimiter.acquire();
               System.out.println("请求 " + finalI + ",时间:" + System.currentTimeMillis());
           }).start();
       }

        // SmoothWarmingUp:预热10秒,令牌生成速率逐渐升高到5 QPS
        RateLimiter warmingUpLimiter = RateLimiter.create(5.0, 10, TimeUnit.SECONDS);

        // 使用warmingUpLimiter
        System.out.println("SmoothWarmingUp模式:");
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            new Thread(() -> {   // 使用burstyLimiter
                warmingUpLimiter.acquire();
                System.out.println("请求 " + i + ",时间:" + System.currentTimeMillis());
            }).start();
        }
    }
}

输出:

SmoothBursty模式:
请求 1,时间:1751290470946
请求 9,时间:1751290471951
请求 7,时间:1751290472950
请求 6,时间:1751290473948
请求 8,时间:1751290474950
请求 5,时间:1751290475948
请求 3,时间:1751290476950
请求 4,时间:1751290477947
请求 2,时间:1751290478947
请求 0,时间:1751290479950

可以看到:guava RateLimiter不保证线程的公平,由于设置的qps=1,所以每个线程打印的时间基本是相差1000.

1.2)api说明:

  • create(double permitsPerSecond):创建一个限流器,参数是每秒允许的请求数(QPS),默认是平滑突发模式。对于平滑突发模式,桶的最大容量=permitsPerSecond。
  • double acquire()/acquire(int permits):请求一个/n个令牌,如果当前没有令牌,则会阻塞直到令牌可用。返回值是获取令牌等待的时间(单位秒)
  • boolean tryAcquire() /tryAcquire(int permits):尝试获取1个/n个令牌,获取不到时立即返回 false,适合非阻塞限流。
  • boolean tryAcquire(int permits, long timeout, TimeUnit unit):获取permits个令牌,最多阻塞等待timeout时间
  • setRate(double permitsPerSecond):设置令牌发放速率。RateLimiter允许在运行中调整速率。

注:RateLimiter 是线程安全的,它会限制所有线程的调用总速率,但它并不保证公平性。

1.3)其他单机限流组件:

  • Resilience4j:功能丰富的限流、熔断、重试库,支持多种限流策略。
  • Bucket4j:基于令牌桶算法的限流库,支持分布式限流。
  • Sentinel(阿里开源):功能强大的流量控制组件,支持多种限流规则。

2、RateLimiter实现原理

传统的令牌桶算法中,有一个固定容量的桶,并以稳定的速率生成令牌放在桶中。当有请求时,需要获取令牌才能通过。因为桶的容量固定,令牌满了就不生产了。桶中有充足的令牌时,突发的流量可以直接获取令牌通过。当令牌取光后,后面的请求就得等生成令牌后才能通过。

RateLimiter的令牌桶算法并不是真的有个令牌桶对象,以每秒定时任务式地往桶中生产指定个数的令牌对象。后面介绍RateLimiter到底是如何以非定时任务的方式实现令牌桶算法的?

2.1)RateLimiter如何生成令牌放到桶里?——延迟计算令牌

1)思路:

根据令牌桶算法,桶中的令牌是持续生成存放的,有请求时需要先从桶中拿到令牌才能开始执行。根据这个特点,很容易想到:

开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如,某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数,这样的开销是巨大的。

RateLimiter使用延迟计算令牌的方式:通过维护一个“下一次可用许可(令牌)的时间戳”(nextFreeTicketMicros)来控制许可发放节奏,同时允许令牌积累,但积累的最大量是有限制的。主要原理是:允许第一个请求预消费桶中的令牌,下一个请求则需要等待生产“上次预消费令牌数量”的时间。

  1. 在每次acquire请求获取特定数量的令牌是,先按照【当前时间和上一次计算令牌的时间的差值/产生令牌的时间间隔】计算出当前时刻桶内应该有多少个令牌(是一个浮点数),将生成的令牌加入令牌桶中并更新令牌数。这样一来,只需要在获取令牌时计算一次令牌数,而不是定时生产令牌。
  2. 计算得到桶内令牌数后,根据请求的令牌数和当前现有的令牌数,通过一定的规则计算出本次请求需要等待的时间是多少(可以是0),然后让请求线程进行阻塞等待这么多时间。(第一个请求可以预消费——不用等待,但后续请求需要先把上次欠的令牌还完后再消费
  3. 请求线程从acquire请求处苏醒,继续执行后续代码。这样,RateLimiter对这个请求做限流的工作就完成了。

注意:请求的许可数量不会影响请求本身的限流(调用 acquire(1) 和调用 acquire(1000) 会导致完全相同的节流,即使两者有差异),但会影响下一个请求的节流。也就是说,如果一个开销较大的任务到达空闲的 RateLimiter,它会立即被授予许可,但下一个请求将受到额外的节流,从而弥补这个开销较大的任务的开销。

2)说明:
  • RateLimiter实际上有两种限流器实现类:SmoothBursty限流器和SmoothWarmingUp限流器。它们的核心区别其实仅在于第2步中,去计算本次请求令牌需要等待的时间的方式是不同的。具体体现为reserveEarliestAvailable()内调用的计算等待时间的方法有不同的重写。
  • 在第2步中,计算出来的等待时间也可能是0,即不需要等待(请求线程不需要被阻塞),请求线程直接能马上获取到令牌。
  • RateLimiter一定保证请求线程能拿到它想要的全部令牌,尽管拿到这些令牌可能需要等一段时间。这个也是比较创新的一点
3)源码结构:

RateLimiter是一个抽象类,SmoothRateLimiter(本身也是一个抽象类)实现了该抽象类;在SmoothRateLimiter中定义了两个static的子实现类,分别是:SmoothWarmingUp和SmoothBursty。查看SmoothRateLimiter,定义了以下属性:

abstract class SmoothRateLimiter extends RateLimiter {
  
  static final class SmoothWarmingUp extends SmoothRateLimiter {
  }
  static final class SmoothBursty extends SmoothRateLimiter {
  }

  /**当前存储令牌数 The currently stored permits. */
  double storedPermits;
  /**最大存储令牌数 The maximum number of stored permits. */
  double maxPermits;
  /**
   * 添加(生成)令牌时间间隔
   * 无论哪个子类,计算方式都是:stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
   */
  double stableIntervalMicros;
  /**
   * 下一次请求可以获取令牌的起始时间,由于RateLimiter允许一次预消费,上次请求预消费令牌后,下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌
   */
  private long nextFreeTicketMicros = 0L; // could be either in the past or future
}

2.2)请求获取令牌过程

RateLimiter本身是线程安全的,在实际业务中

  • 如果你的限流逻辑是全局的,且需要在整个应用程序中共享同一个RateLimiter实例,可以使用单例模式(例如在spring中设置成一个bean)。
  • 如果每个需要限流的业务逻辑都有自己独立的RateLimiter实例,那么需要多个RateLimiter实例。
1)整体流程:

对于某个RateLimiter实例,多个线程并发调用acquire()方式进行流量控制,内部调用链如下:

  1. reserve :每个线程同步阻塞的方式更新桶令牌、nextFreeTicketMicros,以及计算需要等待的时间
  2. sleepMicrosUninterruptibly:每个线程进行等待阻塞(内部通过sleep实现)

所以,从这里可以看到,令牌的发放通过惰性的方式放在了每次请求中,而不是通过定时器来完成

public abstract class RateLimiter {
  public double acquire(int permits) {
    long microsToWait = reserve(permits); //更新桶令牌、nextFreeTicketMicros,以及计算需要等待的时间
    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);
}

说明1:在执行等待的时候,是通过Uninterruptibles.sleepUninterruptibly实现的,它本质上是包装了Thread.sleep(),但是会:

  • 忽略中断(不抛出InterruptedException)
  • 确保睡眠完整的时间
  • 最后恢复中断状态

说明2:当并发场景中,多个线程调用acquire方法时,阻塞会发生在两个阶段:

  1. 同步阶段:在synchronized内计算等待时间、更新桶内令牌数和nextFreeTicketMicros(非常短暂)
  2. 等待阶段:通过sleep让请求的线程等待

这样设计好处是:实际等待在同步块外进行,避免持有锁时睡眠。同步块只用于计算等待时间,不用于实际等待,同步块内确保了令牌计算和状态更新的原子性,防止并发问题

2)reserve方法拆解和同步阻塞中使用的锁:

reserve是RateLimiter最核心的方法,主要功能:生成桶内令牌、根据请求的令牌数计算nextFreeTicketMicros、计算本次请求的等待时间。内部通过synchronized确保多线程的互斥(同步阻塞)。调用链:reserveAndGetWaitLength -> reserveEarliestAvailable:

public abstract class RateLimiter {

    // Can't be initialized in the constructor because mocks don't call the constructor.
    private volatile Object mutexDoNotUseDirectly;
    private Object mutex() {
      Object mutex = mutexDoNotUseDirectly;
      if (mutex == null) {
         synchronized (this) {
           mutex = mutexDoNotUseDirectly;
            if (mutex == null) {
              mutexDoNotUseDirectly = mutex = new Object();
            }
         }
      }
      return mutex;
    }
    //更新桶内令牌、nextFreeTicketMicros和计算本次请求等待时间
    final long reserve(int permits) {
        checkPermits(permits);
        synchronized (mutex()) {
            return reserveAndGetWaitLength(permits, stopwatch.readMicros()); //stopwatch.readMicros()获取的当前时间
        }
    }  
    // 需要等待的时间(单位micros)
    final long reserveAndGetWaitLength(int permits, long nowMicros) {
        long momentAvailable = reserveEarliestAvailable(permits, nowMicros); // 返回当前请求可以获取到令牌的最早可用时间点(单位为微秒)
        return max(momentAvailable - nowMicros, 0); //计算出等待时间,0表示不用等待
    }
    
    abstract long reserveEarliestAvailable(int permits, long nowMicros);
}

RateLimiter使用了双重检验锁的方式创建了一个Object对象,所有线程都利用这个对象来进行资源的互斥,reserve、setRate、getRate、tryAcquire关键操作都通过synchronized同步控制。reserve方法内部方法调用:

  1. 调用rsync方法,根据nowNicros>nextFreeTicketMicros判断是否处于空闲期,从而生成新的令牌;
  2. 将当前nextFreeTicketMicros的值作为reserveEarliestAvailable的返回值(long returnValue = nextFreeTicketMicros;)作为本次获取令牌的最早可用时间;
  3. 根据本次请求的令牌数和桶内已有令牌数,计算出一个生成这些令牌所需的等待时间,并更新nextFreeTicketMicros(作为下次获取令牌的最早可用时间)和桶内令牌数storedPermits
  4. 根据第二部的返回值,通过max(momentAvailable - nowMicros, 0);计算出来本次请求需要等待时间。

注意:两个时间都和nextFreeTicketMicros变量有关,

  • 本次请求等待时间:根据当前nextFreeTicketMicros值计算;
  • 下次获取可用令牌的时间:会根据本次请求的令牌数和桶内令牌数进行计算,然后更新nextFreeTicketMicros值,也就是下次请求需要等待时间。

3)reserveEarliestAvailable方法拆解:

该方法每次会调用rsync做一次令牌生成的逻辑(只有“空闲期”才会生成新令牌)。然后,会根据本次请求的令牌数、桶内已有令牌数(初始值=0)更新nextFreeTicketMicros和桶内令牌数。

abstract class SmoothRateLimiter extends RateLimiter {
    final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
        resync(nowMicros);
        long returnValue = nextFreeTicketMicros;
        double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
        double freshPermits = requiredPermits - storedPermitsToSpend;
        long waitMicros =
            storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
                + (long) (freshPermits * stableIntervalMicros);
    
        this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
        this.storedPermits -= storedPermitsToSpend;
        return returnValue;
    }

    void resync(long nowMicros) {
        // if nextFreeTicket is in the past, resync to now
        if (nowMicros > nextFreeTicketMicros) {
          double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros(); //生成新令牌数
          storedPermits = min(maxPermits, storedPermits + newPermits);
          nextFreeTicketMicros = nowMicros;
        }
    }

}

其中coolDownIntervalMicros()和storedPermitsToWaitTime()这两个方法,前者影响生成新令牌的数量;后者影响下次请求获取可用令牌的时间(也就是下次请求等待时间)。在不同子类中实现方式不同,对于SmoothBursty子类实现的比较简单:

  • coolDownIntervalMicros方法:返回stableIntervalMicros,返回指定令牌速度的时间间隔,stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;(即初始化限流器出入参数permitsPerSecond的倒数)
  • storedPermitsToWaitTime方法:直接返回0

对于SmoothWarmingUp子类实现,【见下面2.3】

A、resync方法:

当nowMicros <= nextFreeTicketMicros:表示有“欠账”不需要发放令牌;当nowMicros > nextFreeTicketMicros,表示没有"欠账",此时可以计算闲置期间应积累的令牌,更新令牌数、nextFreeTicketMicros:

  1. 新进的令牌数newPermits = 闲置时间 / 冷却间隔时间,对于SmoothBursty,冷却间隔时间=stableIntervalMicros,即令牌发放速率间隔时间,也就是空闲时间段该生成多少个令牌
  2. 将新令牌加入存储,不超过最大限制maxPermits,对于SmoothBursty,maxPermits = 1秒的令牌数(也就是创建时指定的参数permitsPerSecond)【见下面2.3】
  3. 将下次令牌可用时间更新为当前时间,由于这个条件是闲置期间,所以可以肯定本次请求不需要等待,从计算等待时间也可以得出来max(momentAvailable - nowMicros, 0);等待时间一定是0

B、reserveEarliestAvailable剩下的方法:

  1. 该方法首先会讲当前的nextFreeTicketMicros赋值给returnValue作为返回值(代码第四行long returnValue = nextFreeTicketMicros; ),从而用来计算本次请求的等待时间。
  2. 根据本次请求的令牌数、桶内已有令牌数(初始值=0),计算出消耗这些令牌需要的时间(waitMicros),然后更新nextFreeTicketMicros(作为计算下次请求等待时间)和桶内令牌数。

对于SmoothBursty子类,waitMicros = freshPermits * stableIntervalMicros,freshPermits是本次请求的令牌数和桶内令牌数差的绝对值,stableIntervalMicros表示令牌发放速率间隔时间(初始化限流器出入参数的倒数)。

示例:5qps场景下的行为:

当设置为5qps时,理论上是每200毫秒(1000/5)允许一个请求,大量请求时:

a. 第一个线程获取令牌并立即通过

b. 后续线程会依次计算需要等待的时间(如200ms, 400ms, 600ms...)

c. 每个线程会在自己的等待时间上睡眠

2.3)SmoothBursty创建过程分析:

SmoothBursty是一种稳定的限流器。

1)源码

public abstract class RateLimiter {
    
  public static RateLimiter create(double permitsPerSecond) {
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
  }
  static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }
  public final void setRate(double permitsPerSecond) {
    synchronized (mutex()) {
      doSetRate(permitsPerSecond, stopwatch.readMicros());
    }
  }
  abstract void doSetRate(double permitsPerSecond, long nowMicros);
}

abstract class SmoothRateLimiter extends RateLimiter {
  @Override
  final void doSetRate(double permitsPerSecond, long nowMicros) {
    resync(nowMicros);
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    doSetRate(permitsPerSecond, stableIntervalMicros);
  }
  
  static final class SmoothBursty extends SmoothRateLimiter {
      final double maxBurstSeconds;
  
      SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
        super(stopwatch);
        this.maxBurstSeconds = maxBurstSeconds;
      }
      @Override
      void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
        double oldMaxPermits = this.maxPermits;
        maxPermits = maxBurstSeconds * permitsPerSecond;
        if (oldMaxPermits == Double.POSITIVE_INFINITY) {
          // if we don't special-case this, we would get storedPermits == NaN, below
          storedPermits = maxPermits;
        } else {
          storedPermits =
              (oldMaxPermits == 0.0)
                  ? 0.0 // initial state
                  : storedPermits * maxPermits / oldMaxPermits;
        }
      }
      @Override
      long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
        return 0L;
      }
      @Override
      double coolDownIntervalMicros() {
        return stableIntervalMicros;
      }
  }
}

A、创建流程:

在创建SmoothBursty时会调用setRate方法,该方法内部会同步调用doRate方法,首先调用rsync,然后计算出stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;

B、在doSetRate方法中,为什么要保存oldMaxPermits?

先保存oldMaxPermits是为了正确处理storedPermits的重新计算。这里有几个关键点:

  1. 当速率改变时,maxPermits会被重新计算(maxPermits = maxBurstSeconds * permitsPerSecond)
    • 但storedPermits需要根据新旧maxPermits的比例进行调整,以保持存储许可的相对比例不变
    • 如果不保存旧值,就无法正确计算新的storedPermits值
  1. 避免NaN出现的场景:
  • 当oldMaxPermits是Double.POSITIVE_INFINITY时,如果不特殊处理,在计算storedPermits * maxPermits / oldMaxPermits时会出现Infinity/Infinity的情况,这会得到NaN。这种情况可能发生在:初始创建RateLimiter时当RateLimiter被配置为无限速率时(比如通过RateLimiter.create(Double.POSITIVE_INFINITY))

2)SmoothBrusty桶大小

令牌桶算法的优势之一就是允许保留一定的令牌数在桶中(最多积累到桶的容量大小),这样就可以应对突增的流量。Google Guava 的 RateLimiter 是基于令牌桶算法实现的,对于SmoothBrusty,允许积累最多1秒的令牌数(也就是创建时指定的参数permitsPerSecond),避免了“因为调用时间稍有延迟导致后续请求必须等待”的问题。即: maxPermits = maxBurstSeconds * permitsPerSecond;其中maxBurstSeconds = 1.

2.4)SmoothWarmingUp创建过程解读:

SmoothWarmingUp 是 Guava RateLimiter 中的一种预热限流算法,它的工作原理可以总结如下:

  • 通过预热机制平滑地从冷启动状态过渡到稳定速率
  • 避免突发流量对系统造成冲击,特别适用于需要"热身"的系统(如缓存预热、JVM JIT编译等场景)

这种算法通过数学建模实现了从冷启动到稳定状态的平滑过渡,既能保护系统不被突发流量冲垮,又能充分利用系统资源。

注:SmoothWarmingUp会根据系统的使用情况动态在预热区和稳定区之间切换

  • 当 RateLimiter 刚创建时,storedPermits 会被初始化为 maxPermits(冷启动状态),第一次请求会消耗大量存储许可,直接进入预热区计算
  • 从冷启动恢复:当系统长期闲置后,storedPermits 会逐渐累积到 maxPermits,此时新的请求会再次进入预热区
  • 突发流量处理:当短时间内消耗大量许可导致 storedPermits < thresholdPermits 时,会自动切换到稳定区
  • 持续稳定状态:当系统保持稳定速率使用时,storedPermits 会维持在 thresholdPermits 以下,持续使用稳定区计算

1)源码:

 

public abstract class RateLimiter {
  public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod) {
    return create(permitsPerSecond, toNanosSaturated(warmupPeriod), TimeUnit.NANOSECONDS);
  }
  public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
    return create(
        permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
  }
  static RateLimiter create(
      double permitsPerSecond,
      long warmupPeriod,
      TimeUnit unit,
      double coldFactor,
      SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }
  public final void setRate(double permitsPerSecond) {
    synchronized (mutex()) {
      doSetRate(permitsPerSecond, stopwatch.readMicros());
    }
  }
  abstract void doSetRate(double permitsPerSecond, long nowMicros);
}

abstract class SmoothRateLimiter extends RateLimiter {
  @Override
  final void doSetRate(double permitsPerSecond, long nowMicros) {
    resync(nowMicros);
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    doSetRate(permitsPerSecond, stableIntervalMicros);
  }

  static final class SmoothWarmingUp extends SmoothRateLimiter {
    private final long warmupPeriodMicros;
    private double slope;
    private double thresholdPermits;
    private double coldFactor;

    SmoothWarmingUp(
        SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit, double coldFactor) {
      super(stopwatch);
      this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);
      this.coldFactor = coldFactor;
    }
    @Override
    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
      double oldMaxPermits = maxPermits;
      double coldIntervalMicros = stableIntervalMicros * coldFactor;
      thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
      maxPermits = thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
      slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
      if (oldMaxPermits == Double.POSITIVE_INFINITY) {
        // if we don't special-case this, we would get storedPermits == NaN, below
        storedPermits = 0.0;
      } else {
        storedPermits = (oldMaxPermits == 0.0) ? maxPermits // initial state is cold
                : storedPermits * maxPermits / oldMaxPermits;
      }
    }
    @Override
    long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
      //见下面 
    }
    private double permitsToTime(double permits) {
      return stableIntervalMicros + permits * slope;
    }
    @Override
    double coolDownIntervalMicros() {
      return warmupPeriodMicros / maxPermits;
    }
  }
}

参数:warmupPeriod,预热时间,表示RateLimiter 在达到稳定(最大)速率之前提高速率的持续时间

  • warmupPeriodMicros:预热时间(微秒)
  • coldFactor:冷启动因子(默认3),表示冷启动时的速率是稳定速率的 1/3
  • thresholdPermits:阈值许可数,区分稳定区和预热区,计算方法:0.5 * warmupPeriodMicros / stableIntervalMicros,也可以理解成:0.5 * permitsPerSecond * warmupPeriod(秒)
  • maxPermits:最大许可数,计算方法:thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros)。

2)工作流程:

  • 令牌存储:
    • 当系统空闲时,会逐渐累积令牌到 maxPermits
  • 许可消耗:
    • 当消耗的令牌数在 thresholdPermits 以下时,使用稳定速率
    • 当消耗的令牌数超过 thresholdPermits 时,进入预热区,速率会逐渐变慢

3)使用梯形面积模型计算等待时间:

SmoothWarmingUp使用了梯型面积模型计算等待时间

  • 稳定区(0 - thresholdPermits):矩形面积,等待时间 = 许可数 × 稳定间隔
  • 预热区(thresholdPermits - maxPermits):梯形面积,等待时间 = (上底+下底)×高/2

如图:

代码: 

@Override
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
  double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
  long micros = 0;
  
  // 预热区计算(梯形面积)
  if (availablePermitsAboveThreshold > 0.0) {
    double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
    double length = 
        permitsToTime(availablePermitsAboveThreshold) +  // 上底
        permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); // 下底
    micros = (long) (permitsAboveThresholdToTake * length / 2.0); // 梯形面积公式
    permitsToTake -= permitsAboveThresholdToTake;
  }
  
  // 稳定区计算(矩形面积)
  micros += (long) (stableIntervalMicros * permitsToTake);
  return micros;
}
private double permitsToTime(double permits) {
  return stableIntervalMicros + permits * slope; // 线性函数计算间隔时间
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值