RateLimiter源码分析

简介: RateLimiter源码分析

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(如果需要的话)


12.png


Demo


//创建一个一秒产生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个令牌


//每秒创建0.1个令牌,即10秒创建一个令牌
RateLimiter rateLimiter = RateLimiter.create(0.1);


其中permitsPersecond  = 0.1


public static RateLimiter create(double permitsPerSecond) {
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());


其中permitsPersecond  = 0.1


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秒


SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
      super(stopwatch);
      this.maxBurstSeconds = maxBurstSeconds;
    }


1.2看rateLimiter.setRate(permitsPerSecond) 方法


此处用了synchronized关键字


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)


@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


 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


@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表示我要获取一个 令牌


public double acquire(int permits) {
    long microsToWait = reserve(permits);
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }


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


final long reserve(int permits) {
    checkPermits(permits);
    synchronized (mutex()) {
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
  }


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


final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    return max(momentAvailable - nowMicros, 0);
  }


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


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方法


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个令牌,此时进入下面的方法


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


目录
相关文章
CountDownLatch实现原理全面解析
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步(即:用于线程之间的通信而不是互斥)。它允许一个或多个线程进入等待状态,直到其他线程执行完毕后,这些等待的线程才继续执行。
|
存储 缓存 监控
线程池原理初探以及源码分析(详解)
线程池原理初探以及源码分析(详解)
100 0
|
缓存 算法 API
【如何】guava的RateLimiter使用
【如何】guava的RateLimiter使用
99 0
|
存储 算法 Java
【Java技术指南】「并发编程专题」Guava RateLimiter针对于限流器的入门到精通(含源码分析介绍)
【Java技术指南】「并发编程专题」Guava RateLimiter针对于限流器的入门到精通(含源码分析介绍)
143 0
【Java技术指南】「并发编程专题」Guava RateLimiter针对于限流器的入门到精通(含源码分析介绍)
|
算法 Java
【Java技术开发专题】系列之「Guava RateLimiter」针对于限流器的入门到实战(含源码分析介绍)
【Java技术开发专题】系列之「Guava RateLimiter」针对于限流器的入门到实战(含源码分析介绍)
242 0
【Java技术开发专题】系列之「Guava RateLimiter」针对于限流器的入门到实战(含源码分析介绍)
|
缓存 安全 Java
ConcurrentHashMap源码解读
ConcurrentHashMap源码解读
ConcurrentHashMap源码解读
|
存储 算法 Java
超详细的Guava RateLimiter限流原理解析
限流是保护高并发系统的三把利器之一,另外两个是缓存和降级。限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统不被巨型流量冲垮等。
|
算法 前端开发 Java
实战限流(guava的RateLimiter)
guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,本文实战一下RateLimiter的用法
1263 0
实战限流(guava的RateLimiter)
|
算法 Java Windows
Guava-RateLimiter详解
常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败,下图来自网上: ratelimite原理图 本次实战,我们用的是guava的RateLimiter,场景是spring mvc在处理请求时候,从桶中申请令牌,申请到了就成功响应,申请不到时直接返回失败。
2954 0
|
存储 算法 测试技术
源码分析 RateLimiter SmoothBursty 实现原理(文末附流程图)
源码分析 RateLimiter SmoothBursty 实现原理(文末附流程图)
源码分析 RateLimiter SmoothBursty 实现原理(文末附流程图)