GUAVA之RateLimiter类 源码阅读笔记

这段时间因为业务需要,去学习使用了RateLimiter类来控制访问,发现十分方便好用,因此打算学习一下是如何实现的。

RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。
重点在于控制的速率。

基础算法

是如何实现这种对速率的控制的呢?这就跟RateLimiter的设计的算法——令牌桶算法 and 漏桶算法

令牌桶算法

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,当一个请求进来的时候,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

1.每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌(不是某个时间点一下子给出该秒所有的令牌)
2.桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被丢弃
3.当一个 n 字节的数据包到达时,消耗 n 个令牌,然后发送该数据包
4.如果桶中可用令牌小于 n,则该数据包将被缓存或丢弃

漏桶算法

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

区别:
漏桶算法能够强行限制数据的传输速率。
令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。

RateLimiter的使用:

基本使用手册里都有详细描述 ,这里主要说一下之前遇到的问题
使用RateLimiter的时候会在最开始的时候create一个规定的速率。
这就是对速率的控制。

//每秒5个令牌。以每0.2/个令牌的速度放进去
RateLimiter rateLimiter = RateLimiter.create(5.0);

关于acquire 和 tryAcquire
double a = rateLimiter.acquire();
boolean b= rateLimiter.tryAcquire();

最开始,在代码里使用了acquire方法,然后发现有些请求十分缓慢,后来发现是多余的请求会被阻塞,返回被阻塞的时间。但是这样会影响正常的访问。之后改成了tryAcquire,tryAcquire是非阻塞的,如果不能即时获取到令牌,会直接失败,然后直接将返回false的请求给throw 出来。这样去控制一些访问的请求数。

比如 我们按照我们上述规定的5个/s ,一秒内5个请求进来,每隔0.2s就可以允许一个请求,如果超过5个,那么就需要等待,如果用acquire,那么第六个请求需要等1.2秒之后才能获取到令牌,这1.2s里 这个请求是在等待的。但是用tryAcquire的话就会立即返回false;

RateLimiter的实验:

       RateLimiter rateLimiter = RateLimiter.create(5);
            double a=rateLimiter.acquire(1);
            System.out.println("0 = "+a);

            double b=rateLimiter.acquire(3);
            System.out.println("1 = "+b);

            double c=rateLimiter.acquire(1);
            System.out.println("3 = "+c);

            double d=rateLimiter.acquire(18);
            System.out.println("4 = "+d);

            double e=rateLimiter.acquire(1);
            System.out.println("5 = "+e);

结果:

0 = 0.0 //第一个是预支的 
1 = 0.194598 //因为第一个预支了,所以第二次要的等1/5s
3 = 0.596369 //同理 第二次取了3个 就需要给1/5 * 3 s
4 = 0.198327
5 = 3.597525

和Semaphore 的区别

这个是JDK 里 concurrent下的一个类,也是可以指定一个令牌总数。多线程情况下,每个线程要继续执行,需要获取这个令牌。每个线程用完了放回去。主要是控制并发总数。
这里RateLimiter 是控制一个速率

RateLimiter的源码:

SmoothBursty:恒定的速度放入令牌
SmoothWarmingUp:缓慢提升到一定值放入令牌
区别:可以控制速率
RateLimiter 中的时间窗口能且仅能为 1s,可以见下面源码分析。

 @VisibleForTesting
  static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
  //可以看这里是写死的1s;
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
//这里才是主要设置令牌数的地方
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }


 static final class SmoothBursty extends SmoothRateLimiter {
    /** The work (permits) of how many seconds can be saved up if this RateLimiter is unused? */
    final double maxBurstSeconds;

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

    @Override
    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
      double oldMaxPermits = this.maxPermits;
    //允许保存的最大的permit 
      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;
      }
    }



 public double acquire(int permits) {
    long microsToWait = reserve(permits); 先计算获取这些请求需要让线程等待多长时间
    stopwatch.sleepMicrosUninterruptibly(microsToWait); //让线程阻塞microTowait微秒长的时间
    return 1.0 * microsToWait / SECONDS.toMicros(1L);//返回阻塞的时间
  }


 final long reserve(int permits) {
    checkPermits(permits);//主要判断permits为正
    synchronized (mutex()) { 
//stopwatch 这个是到当前为止 等待睡眠的时间
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
  }


//返回需要等待的时间(正数)
  final long reserveAndGetWaitLength(int permits, long nowMicros) {
//获取持续的时间
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    return max(momentAvailable - nowMicros, 0);
  }



//返回
  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    resync(nowMicros); //补充令牌
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
//需要重新生成多少个permit
    double freshPermits = requiredPermits - storedPermitsToSpend;
//生成freshPermits个permit需要的时间 这个时间就是下次我们需要减去的时间  
  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 (nowMicros > nextFreeTicketMicros) {
    // nextFreeTicketMicros的意思是:下次获取的时候需要减去的时间
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
//storedPermits能保存的permit
//就是过去一段时间的利用不足的storedPermits 最多不超过create的permit
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269