Interviewer: talk about limiting -RateLimiter source code analysis

RateLimiterThere are two implementation classes: SmoothBurstyand SmoothWarmingUpvariants, which are implemented in the token bucket algorithm, except that the SmoothBurstyaddition speed of the token is constant, and SmoothWarmingUpwill have a warm-up period, the warm-up period token plus slow speed slow increase until reaches a fixed speed. Its application scenario is smaller for some systems can withstand just start QPS, warm up after a period of time to achieve the best condition.

 

Basic use

RateLimiter very simple to use: 

// Create method is the number of incoming tokens generated per 
RateLimiter rateLimiter RateLimiter.create = (. 1 );
 for ( int I = 0; I <. 5; I ++ ) {
     // Acquire method is the number of incoming tokens needed, when there is insufficient tokens waiting, the process returns to wait for time 
    Double the waitTime rateLimiter.acquire = (. 1 ); 
    System.out.println (System.currentTimeMillis () / 1000+ "," + the waitTime); 
}

 

Output is as follows:

1548070953 , 0.0
1548070954 , 0.998356
1548070955 , 0.998136
1548070956 , 0.99982

 

Note that, when there is insufficient tokens acquiremethod and does not block this call, but will be counted in the next call of the head. For example, the first call, the token bucket and not a token, but has not blocked the first call, but when the second call blocking one second. In other words, under the token each call (if the token bucket is insufficient) is to make the next call to foot the bill.

RateLimiter rateLimiter= RateLimiter.create(1);
double waitTime=rateLimiter.acquire(1000);
System.out.println(System.currentTimeMillis()/1000+" , "+waitTime);
waitTime=rateLimiter.acquire(1);
System.out.println(System.currentTimeMillis()/1000+" , "+waitTime);

 

Output is as follows:

1548072250 , 0.0
1548073250 , 999.998773

 

The purpose of this design are:

Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much better approach is to /allow/ the request right away (as if it was an acquire(1) request instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the task immediately, and postpone by 100 seconds future requests, thus we allow for work to get done in the meantime instead of waiting idly.

Simply put, if each request based pay will be unnecessary waiting times. For example, a token increase in speed of 1 per second, initially no token bucket, then came a request requires 100 tokens, it needs to begin the task wait 100s. Therefore, a better approach is to first release the request, then the request after a delay.

In addition, RateLimiter there is a tryAcquiremethod returns true if the token enough immediately, otherwise immediately return false.

Source code analysis

This article analyzes the SmoothBurstyimplementation.

First look at SmoothBurstyseveral key fields of:

// 桶中最多存放多少秒的令牌数
final double maxBurstSeconds;
//桶中的令牌个数
double storedPermits;
//桶中最多能存放多少个令牌,=maxBurstSeconds*每秒生成令牌个数
double maxPermits;
//加入令牌的平均间隔,单位为微秒,如果加入令牌速度为每秒5个,则该值为1000*1000/5
double stableIntervalMicros;
//下一个请求需要等待的时间
private long nextFreeTicketMicros = 0L;

RateLimiter creation

Look at the creation of the create method RateLimiter.

// permitsPerSecond to generate a number of tokens per second 
public  static RateLimiter Create ( Double permitsPerSecond) {
     return Create (permitsPerSecond, SleepingStopwatch.createFromSystemTimer ()); 
} 

// SleepingStopwatch mainly used for timing and sleep 
static RateLimiter Create ( Double permitsPerSecond, SleepingStopwatch Stopwatch) {
     // create a SmoothBursty 
    rateLimiter rateLimiter = new new SmoothBursty (Stopwatch, 1.0 / * maxBurstSeconds * / ); 
    rateLimiter.setRate (permitsPerSecond); 
    return rateLimiter; 
}
create the main method is to create a SmoothBursty instance and call its setRate method. Note here maxBurstSeconds-coded to 1. 0 . 

@Override 
Final  void doSetRate ( Double permitsPerSecond, Long nowMicros) { 
    the resync (nowMicros); 
    Double stableIntervalMicros = SECONDS.toMicros (1L) / permitsPerSecond;
     the this .stableIntervalMicros = stableIntervalMicros; 
    doSetRate (permitsPerSecond, stableIntervalMicros); 
} 

void the resync ( Long nowMicros) {
     // If the current time is greater than nextFreeTicketMicros, instructions owe a token request has been supplemented well, this request without waiting for the 
    IF (nowMicros>nextFreeTicketMicros) {
       // calculated for the time required to add the token, coolDownIntervalMicros returns stableIntervalMicros 
      Double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros ();
      // update token bucket, no more than maxPermits 
      storedPermits = min ( maxPermits, storedPermits + newPermits);
       // where the first set nowMicros 
      nextFreeTicketMicros = nowMicros; 
    } 
} 

@Override 
void doSetRate ( Double permitsPerSecond, Double stableIntervalMicros) {
     Double oldMaxPermits = the this .maxPermits; 
    maxPermitsMaxBurstSeconds * = permitsPerSecond;
     IF (oldMaxPermits == Double.POSITIVE_INFINITY as) {
         // IF WE do Special Not the this-Case, WE GET storedPermits == Would NaN3, below 
        storedPermits = maxPermits; 
    } the else {
         // first call oldMaxPermits is 0, storedPermits (the number of token bucket) is also 0 
        storedPermits = 
                (oldMaxPermits == 0.0 )
                         0.0? // Initial State 
                        : storedPermits * maxPermits / oldMaxPermits; 
    } 
}

 

setRateThe method set maxPermits=maxBurstSeconds * permitsPerSecond; and maxBurstSeconds1, so that maxBurstSeconds only the number of tokens stored in one second.

Note that the SmoothBurstynon-public classes, that only through the RateLimiter.createcreation method, and the method maxBurstSeconds is to write the death of 1.0, which means that we can only create the bucket size is permitsPerSecond * 1 of SmoothBurstyobjects (of course, not in the way of reflection scope), there are several issue (in guava's github warehouse issue1 , Issue 2 , issue3 , issue4 ) want to set externally maxBurstSeconds , but did not see the return of officials. In open source projects only product will vjtools , it was raised this question , the students will be the only product of guava is RateLimiter were expanded .

For this design guava I did not understand, there are clear friends can say the next ~

A far SmoothBurstyobject is created, then we analyze its acquiremethods.

acquire method

public double acquire(int permits) {
    // 计算本次请求需要休眠多久(受上次请求影响)
    long microsToWait = reserve(permits);
    // 开始休眠
    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);
}

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    // 这里调用了上面提到的resync方法,可能会更新桶中的令牌值和nextFreeTicketMicros
    resync(nowMicros);
    // 如果上次请求花费的令牌还没有补齐,这里returnValue为上一次请求后需要等待的时间,否则为nowMicros
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // 缺少的令牌数
    double freshPermits = requiredPermits - storedPermitsToSpend;
    // waitMicros为下一次请求需要等待的时间;SmoothBursty的storedPermitsToWaitTime返回0
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);
    // 更新nextFreeTicketMicros
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    // 减少令牌
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
}

 

acquire中会调用reserve方法获得当前请求需要等待的时间,然后进行休眠。reserve方法最终会调用到reserveEarliestAvailable,在该方法中会先调用上文提到的resync方法对桶中的令牌进行补充(如果需要的话),然后减少桶中的令牌,以及计算这次请求欠的令牌数及需要等待的时间(由下次请求负责等待)。

如果上一次请求没有欠令牌或欠的令牌已经还清则返回值为nowMicros,否则返回值为上一次请求缺少的令牌个数*生成一个令牌所需要的时间。

End

本文讲解了RateLimiter子类SmoothBursty的源码,对于另一个子类SmoothWarmingUp的原理大家可以自行分析。相对于传统意义上的令牌桶,RateLimiter的实现还是略有不同,主要体现在一次请求的花费由下一次请求来承担这一点上。

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

Guess you like

Origin www.cnblogs.com/yuxiang1/p/11347015.html