RateLimiter源码分析

2023-12-25 18:59:43 浏览数 (2)

RateLimiter的核心思路

如下图所示,我创建一个1秒产生0.1的RateLimiter(即10秒产生1个),左边是时间轴,现在有3个线程申请数据,nextFreeTicketMicros初始化为0(其实他的计算单位是微秒)

当1点1分0秒时,此时nextFreeTicketMicros = 0秒,线程A开始申请数据,当线程A申请到数据后会把nextFreeTicketMicros设置成10秒(别问为什么设置成10秒),此时线程A执行完毕

当1点1分3秒时,此时nextFreeTicketMicros = 10秒,线程B开始申请数据,因为此时nextFreeTicketMicros = 10秒,然后把nextFreeTicketMicros 设置为20秒(别问为什么设置成20秒) ,RateLimiter里面的时间计数器为3秒,所以要睡7秒(就是线程B红色的地方在睡觉),此时线程B执行完毕

当1点1分7秒时,此时nextFreeTicketMicros = 20秒,线程C开始申请数据,因为此时nextFreeTicketMicros = 20秒,然后nextFreeTicketMicros设置成30秒(别问为什么设置成30秒),RateLimiter里面的时间计数器为7秒,所以要睡14秒(就是线程C红色的地方在睡觉),此时线程C执行完毕

注意:

分析的上面这种情况下,就是每秒产生0.1个。

先设置nextFreeTicketMicros,在sleep(如果需要的话)

Demo

代码语言:javascript复制
//创建一个一秒产生0.1个令牌的实例
RateLimiter rateLimiter = RateLimiter.create(0.1);
        //0秒拿到令牌
        System.out.println(rateLimiter.acquire(1));
        //10秒拿到令牌
        System.out.println(rateLimiter.acquire(1));
        //20秒拿到令牌
        System.out.println(rateLimiter.acquire(1));

RateLimiter初始化

1调用RateLimiter.create方法,传入参数0.1,表示每秒允许0.1个令牌,即10秒创建1个令牌

代码语言:javascript复制
//每秒创建0.1个令牌,即10秒创建一个令牌
RateLimiter rateLimiter = RateLimiter.create(0.1);

其中permitsPersecond = 0.1

代码语言:javascript复制
public static RateLimiter create(double permitsPerSecond) {
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());

其中permitsPersecond = 0.1

代码语言:javascript复制
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }

1.1 看 RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);方法

其中maxBurstSeconds = 1.0秒,表示放入令牌的频率是1.0秒

代码语言:javascript复制
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
      super(stopwatch);
      this.maxBurstSeconds = maxBurstSeconds;
    }

1.2看rateLimiter.setRate(permitsPerSecond) 方法

此处用了synchronized关键字

代码语言:javascript复制
public final void setRate(double permitsPerSecond) {
    checkArgument(
        permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
    synchronized (mutex()) {
      doSetRate(permitsPerSecond, stopwatch.readMicros());
    }
  }

下面跟doSetRate方法

其中permitsPerSecond = 0.1 stopwatch.readMicros即nowMicros为当前的时间戳

stableIntervalMicros = 1秒 / 0.1 ,即生成每一个令牌的平均时间,单位为纳秒(毫秒的1000分之1)

代码语言:javascript复制
@Override
  final void doSetRate(double permitsPerSecond, long nowMicros) {
    resync(nowMicros);
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    doSetRate(permitsPerSecond, stableIntervalMicros);
  }

1.2.1 跟resync方法

啥也没干,就是把当前时间戳赋值给nextFreeTicketMicros

代码语言:javascript复制
 void resync(long nowMicros) {
     //执行到此处,nowMicros为当前时间戳,nextFreeTicketMicros还没有赋值,即初始化为0
    if (nowMicros > nextFreeTicketMicros) {
      //进入该分支,但是coolDownIntervalMicros方法返回的是0,所以newPermits=Infinity
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      storedPermits = min(maxPermits, storedPermits   newPermits);
      //把当前时间戳赋值给nextFreeTicketMicros
      nextFreeTicketMicros = nowMicros;
    }
  }

1.2.2 跟doSetRate(permitsPerSecond, stableIntervalMicros)方法

参数:permitsPerSecond = 0.1 stableIntervalMicros为上面算的 1秒/0.1 = 1.0E7 纳秒

该段逻辑就是给 storedPermits赋值为0

代码语言:javascript复制
@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;
      }
    }

构造方法总结:赋值

maxBurstSeconds = 1.0 令牌桶算法不是有一个定期往桶里放令牌吗,这个参数就时间周期

stableIntervalMicros = 1秒 / 0.1 = 1.0E7纳秒 生成每一个令牌需要多久

nextFreeTicketMicros = 当前时间戳A

storedPermits = 0 当前存储的令牌为 0个

获取锁方法

流程

rateLimiter.acquire(1)

1、其中 permits = 1表示我要获取一个 令牌

代码语言:javascript复制
public double acquire(int permits) {
    long microsToWait = reserve(permits);
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }

2、往下跟,其中permits = 1,表示我要获取的一个令牌

代码语言:javascript复制
final long reserve(int permits) {
    checkPermits(permits);
    synchronized (mutex()) {
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
  }

3、往下跟,其中permits = 1表示我要获取一个令牌,nowMicros表示此时此刻的时间

代码语言:javascript复制
final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    return max(momentAvailable - nowMicros, 0);
  }

4、往下跟,其中permits = 1表示我要获取一个令牌,nowMicros表示此时此刻的时间

代码语言:javascript复制
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    //更新nextFreeTicketMicros,很重要
    resync(nowMicros);
    //用于计算要sleep的时间
    long returnValue = nextFreeTicketMicros;
    //此时此刻能给你几个令牌   min(你要的和他有的最小值)
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    //还需要多少个令牌
    double freshPermits = requiredPermits - storedPermitsToSpend;
    //还需要多少秒
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
              (long) (freshPermits * stableIntervalMicros);

    //更新nextFreeTicketMicros
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    //拥有的令牌 - 分配出去的令牌
    this.storedPermits -= storedPermitsToSpend;
     //用于计算睡觉时间
    return returnValue;
  }

5、 跟resync方法

代码语言:javascript复制
void resync(long nowMicros) {
    // 当前时间大于nextFreeTicketMicros,说明很久没人用了
    if (nowMicros > nextFreeTicketMicros) {
      //计算这个范围内生成的令牌个数
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      //设置令牌个数
      storedPermits = min(maxPermits, storedPermits   newPermits);
      //设置nextFreeTicketMicros
      nextFreeTicketMicros = nowMicros;
    }
  }

核心方法讲解(上面的四)

场景:每一秒产生1个令牌,然后线程A在0秒的申请了一个令牌,10秒以后B来了,要申请10个令牌,此时进入下面的方法

代码语言:javascript复制
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    //更新nextFreeTicketMicros=10秒
    resync(nowMicros);
    //用于计算要sleep的时间
    long returnValue = nextFreeTicketMicros;
    //此时你需要10个令牌,系统有1个令牌,允许分配给你1个令牌
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    //还需要9个令牌
    double freshPermits = requiredPermits - storedPermitsToSpend;
    //还需要等待9个*1秒 = 9 秒
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
              (long) (freshPermits * stableIntervalMicros);

    //更新nextFreeTicketMicros,加9秒,即为19秒
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    //拥有的令牌 - 分配出去的令牌
    this.storedPermits -= storedPermitsToSpend;
     //用于计算睡觉时间
    return returnValue;
  }

总结

1 这种懒加载计算的方法其实很常见,比如懒汉的单例模式,redis里的惰性删除

2 文章写的很烂,其实自己去跟源码才是最好的,别人写的都是转述,三人成虎,只有自己理解的才是最正确的。

3 本文只考虑了SmoothBursty,其实还有SmoothWarmingUp这种,我太菜了,没看懂。

源码下载

ChaiRongD/Demooo - Gitee.com

0 人点赞