Basic learning of Guava RateLimter
Smooth burst current limiting
public static void main(String[] args) { RateLimiter rateLimiter = RateLimiter.create(5); while (true) { double acquire = rateLimiter.acquire(); System.out.println(acquire); } } /** 0.0 0.198756 0.19432 0.199067 */
It can be seen that the token is not given at one time, but distributed at a fixed rate in this second, so each request is about 0.2s. However, RateLimiter has the function of accumulation. When the bucket is not full, RateLimiter will distribute until the bucket is full. At this time, if there is a request, as long as the token is not distributed, the token can be obtained immediately. Therefore, RateLimiter can deal with sudden traffic.
public static void main(String[] args) throws InterruptedException { RateLimiter rateLimiter = RateLimiter.create(5); Thread.sleep(1000); while (true) { double acquire = rateLimiter.acquire(); System.out.println(acquire); } } /** 0.0 0.0 0.0 0.0 0.0 0.0 0.198676 0.194248 */
RateLimiter has another feature. When there are not enough tokens to be issued, it adopts the method of delay processing. For example, there are only three tokens, but now there is a request for four tokens, which can respond immediately. First borrow one token from RateLimiter. The waiting time for this request to obtain the token is borne by the next request, that is, to wait instead of the previous request.
public static void main(String[] args) throws InterruptedException { RateLimiter rateLimiter = RateLimiter.create(6); Thread.sleep(1000); System.out.println(rateLimiter.acquire(3)); System.out.println(rateLimiter.acquire(4)); //The token is not enough, but it can still return immediately System.out.println(rateLimiter.acquire(1)); } /** 0.0 0.0 0.163041 */
Smooth preheating current limiting
Let's take a look at the source code
//Number of tokens requested by permitsPerSecond, warm-up time of warmupPeriod, unit warm-up interval unit public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) { Preconditions.checkArgument(warmupPeriod >= 0L, "warmupPeriod must not be negative: %s", new Object[]{warmupPeriod}); return create(RateLimiter.SleepingStopwatch.createFromSystemTimer(), permitsPerSecond, warmupPeriod, unit); }
For the smooth current limiting with preheating period, there will be a preheating period after it is started. The frequency and time of issuing token will be in a gradient. After the time passes the warmup period, it will reach the originally set frequency. This function is mainly suitable for the scene where the system needs a little time to "warm up" just after startup.
RateLimiter rateLimiter = RateLimiter.create(6, 2, TimeUnit.SECONDS); while (true) { System.out.println(rateLimiter.acquire(2)); } /** 0.0 0.888101 0.660152 0.439506 It just adds up to about 2 seconds 0.328413 Warm up 0.329504 0.329148 */
Principle analysis - Taking smooth burst current limiting as an example
Let's start with acquire
public double acquire() { return this.acquire(1); } public double acquire(int permits) { // Return to the time you need to wait long microsToWait = this.reserve(permits); //Sleep block this.stopwatch.sleepMicrosUninterruptibly(microsToWait); //Return waiting time converted to seconds return 1.0D * (double)microsToWait / (double)TimeUnit.SECONDS.toMicros(1L); } final long reserve(int permits) { checkPermits(permits); //Check whether the limits are reasonable > 0 synchronized(this.mutex()) { //lock return this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros()); //Return time to wait //this.stopwatch.readMicros() current time } } final long reserveAndGetWaitLength(int permits, long nowMicros) { long momentAvailable = this.reserveEarliestAvailable(permits, nowMicros); //Calculate the closest time to get the token return Math.max(momentAvailable - nowMicros, 0L);//Return time to wait }
Next, let's see how reserveearliestavailable calculates the time when the token can be obtained recently
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) { // The number of refresh tokens, which is equal to the refresh of tokens based on time each time acquire resync(nowMicros); //nextFreeTicketMicros can get the time member property of the token in the next request long returnValue = nextFreeTicketMicros; // By comparing the number of existing tokens with the number of target tokens to be obtained, the number of tokens that can be obtained at present can be calculated. double storedPermitsToSpend = min(requiredPermits, this.storedPermits); // Freshlimits is a token that needs to be paid in advance, that is, the number of target tokens minus the current number of tokens // A certain number of tokens will be paid in advance because a large number of requests will flow in suddenly and the number of existing tokens is not enough double freshPermits = requiredPermits - storedPermitsToSpend; // waitMicros is the time to generate the number of advance payment tokens, so the time to add the next token should be calculated plus wattmicros //stableIntervalMicros time interval to add token //The storedPermitsToWaitTime of SmoothBuresty directly returns 0, so watimes is the waiting time of the pre paid token, but it is different in SmoothWarmingUp, which is used to realize the warm-up buffer period long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long) (freshPermits * stableIntervalMicros); try { // Update nextFreeTicketMicros. The waiting time for this pre paid token allows the next request to actually wait. this.nextFreeTicketMicros = LongMath.checkedAdd(nextFreeTicketMicros, waitMicros); } catch (ArithmeticException e) { this.nextFreeTicketMicros = Long.MAX_VALUE; } // Number of update tokens, minimum 0 this.storedPermits -= storedPermitsToSpend; // Return the old nextFreeTicketMicros value without adding the waiting time for the pre paid token. return returnValue; } // SmoothBurest long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { return 0L; } //Number of update tokens void resync(long nowMicros) { // The current time is later than nextFreeTicketMicros, so refresh token and nextFreeTicketMicros if (nowMicros > nextFreeTicketMicros) { // coolDownIntervalMicros function generates a token every few seconds. SmoothWarmingUp and smoothbursty are implemented differently // coolDownIntervalMicros of smoothbursty directly returns stableIntervalMicros // Subtract the time to update the token from the current time and divide it by the time to add the token storedPermits = min(maxPermits, storedPermits + (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros()); nextFreeTicketMicros = nowMicros; } // If the current time is earlier than nextFreeTicketMicros, the thread that gets the token will wait until nextFreeTicketMicros, which needs to get the token // The extra waiting time is replaced by the next thread acquired. } //Returns the time interval between token generation double coolDownIntervalMicros() { return stableIntervalMicros; }