SmoothBursty was introduced in detail in the previous article. This article will introduce SmoothBursty with a warm-up mechanism.
This program recording
-
- 1, the class diagram
- SmoothWarmingUp creation process
- SmoothWarmingUp Acquire process
- 4, summarize
1, the class diagram
Class diagrams related to RateLimiter have also been described in detail above, so this article will not cover them in detail.
SmoothWarmingUp creation process
The entry point for creating a SmoothWarmingUp limiter is the RateLimiter create method, which looks like this: RateLimiter#create
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) { / / @ 1
checkArgument(warmupPeriod >= 0."warmupPeriod must not be negative: %s", warmupPeriod);
return create(
SleepingStopwatch.createFromSystemTimer(), permitsPerSecond, warmupPeriod, unit, 3.0);
}
Copy the code
@1: Let’s look at the argument list first:
- Double permitsPerSecond the number of licenses issued per second, known as QPS.
- Long warmupPeriod warmupPeriod.
- TimeUnit unit warmupPeriod TimeUnit.
Code @2: Call the internal overloaded method to create SmoothWarmingUp.
RateLimiter#create
static RateLimiter create( SleepingStopwatch stopwatch, double permitsPerSecond, long warmupPeriod, TimeUnit unit, double coldFactor) {
RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor); / / @ 1
rateLimiter.setRate(permitsPerSecond); / / @ 2
return rateLimiter;
}
Copy the code
The two main steps to create a SmoothWarmingUp are to call its constructor first to create an instance of SmoothWarmingUp and then to call its setRate method to initialize the rate. ColdFactor is highlighted here, and the default is 3.0, the purpose of which is described in more detail below.
Let’s first focus on the implementation of the setRate method. The doSetRate method of its parent SmoothRateLimiter is eventually called.
SmoothRateLimiter#doSetRate
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros); / / @ 1
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros; / / @ 2
doSetRate(permitsPerSecond, stableIntervalMicros); / / @ 3
}
Copy the code
Code @ 1: Reset storeDperps and nextFreeTicketMicros within SmoothRateLimiter based on the current time, By free, you mean that you don’t have to wait to get permission to set your rate, which is key to understanding the generation of limiting permissions, more on that later.
@2: Calculate a stable time to obtain 1 license according to QPS. Issue 5 permits per second, i.e. speed limit is 5QPS, then the world interval for issuing a permit is 200ms, and the stableIntervalMicros variable is measured in subtlets.
Code @4: Call SmoothRateLimiter’s abstract method doSetRate to set the rate, which will call SmoothWarmingUp’s doSetRate method.
Before introducing the doSetRate method for SmoothWarmingUp, let’s take a look at the implementation of the Resync method.
SmoothRateLimiter#resync
void resync(long nowMicros) {
if (nowMicros > nextFreeTicketMicros) { / / @ 1
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros(); / / @ 2
storedPermits = min(maxPermits, storedPermits + newPermits); / / @ 3
nextFreeTicketMicros = nowMicros; / / @ 4}}Copy the code
Code @1: If the current startup time is longer than nextFreeTicketMicros (the next time you can get a license for free), then the license needs to be recalculated, that is, you can add the license to the license pool again.
Code @2: The number of licenses that can be added based on the current time. Due to SmoothWarmingUp’s implementation of the warm-up mechanism, the average time to generate a license is not fixed. This is implemented by the coolDownIntervalMicros method, which will be explained later.
Code @3: Calculates the currently available licenses and adds the newly added licenses to the license pool, but does not exceed their maximum value.
Code @4: The next update increases the time for calculating permissions.
SmoothWarmingUp#coolDownIntervalMicros
double coolDownIntervalMicros(a) {
return warmupPeriodMicros / maxPermits;
}
Copy the code
The implementation of this method is simple: divide the total time it took to generate these licenses by the number of licenses that have been generated so far to get the average generation time of one license at the current point in time.
Next, focus on the doSetRate method of SmoothWarmingUp.
SmoothWarmingUp doSetRate = SmoothWarmingUp doSetRate
SmoothWarmingUp notes to illustrate some of the main points of this image.
- There are two shaded areas in the figure, one is stable and the other is warm up period. In the preheating algorithm, the relationship between the two shadow areas is related to the cooling factor.
- ColdFactor represents the ratio of coldIntervalMicros to stableIntervalMicros.
- The ratio of warm up period shadow area to stable shadow area is equal to (coldIntervalMicros – stableIntervalMicros)/stableIntervalMicros, For example, if SmoothWarmingUp has a fixed cooling factor of 3, then the ratio of coldIntervalMicros to stableIntervalMicros is 3. So coldIntervalMicros minus stableIntervalMicros over stableIntervalMicros is equal to 2.
- In the warm-up algorithm, it is related to the integral in mathematics (the author has no knowledge of mathematics in this area), so only the conclusion is shown here, and the area of the shadow WARM UP PERIOD is equal to warmupPeriod, and the area of the shadow stable is equal to warmupPeriod/2.
- WarmupPeriod /2 = thresholdPermits * stableIntervalMicros
- WarmupPeriod = 0.5 * (stableInterval + coldInterval) * (maxpermit-thresholdpermits) (top bottom + Bottom bottom * height / 2)
With that in mind, let’s take a look at the code.
SmoothWarmingUp#doSetRate
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = maxPermits;
double coldIntervalMicros = stableIntervalMicros * coldFactor; / / @ 1
thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros; / / @ 2
maxPermits =
thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros); / / @ 3
slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits); / / @ 4
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
storedPermits = 0.0;
} else {
storedPermits =
(oldMaxPermits == 0.0)? maxPermits// initial state is cold
: storedPermits * maxPermits / oldMaxPermits; / / @ 5}}Copy the code
Code @1: Calculate the cooling interval (in microseconds) from the cooling factor, equal to the product of the cooling factor and stableIntervalMicros. From this we can derive the following basic concepts. Cooling factor coldFactor is the ratio of cooling interval to stable interval.
ThresholdPermits @2: Find thresholdPermits by warmupPeriod/2 = thresholdPermits * stableIntervalMicros
Code @3: warmupPeriod = 0.5 * (stableInterval + coldInterval) * (maxpermit-thresholdpermits)
@4: slope, indicating the growth rate of the number of permits from thresholdPermits to maxPermits from stableIntervalMicros to coldIntervalMicros.
Code @5: Update current stored licenses with maxPermits, i.e. the current number of permits remaining to be consumed.
SmoothWarmingUp Acquire process
First, acquire is defined in its parent class, which is a typical template mode. The parent class defines the basic process, and the specific subclass realizes its specific functions. The acquire method in RateLimiter is as follows:
public double acquire(int permits) {
long microsToWait = reserve(permits); / / @ 1
stopwatch.sleepMicrosUninterruptibly(microsToWait); / / @ 2
return 1.0 * microsToWait / SECONDS.toMicros(1L); / / @ 3
}
Copy the code
Code @1: Determine the waiting time for this application according to the remaining license and the license of this application. If 0 is returned, it means that there is no waiting.
Code @2: If the waiting time is not 0, it means that the speed limit is triggered and the sleep will wake up after the specified time.
Code @3: Returns the waiting time for this application.
Next, the implementation principle of reserve method is introduced.
RateLimiter#reserve
inal long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) { / / @ 1
return reserveAndGetWaitLength(permits, stopwatch.readMicros()); / / @ 2}}Copy the code
Code @1: storedPermits major maintenance data field of the limiter. Locks need to be obtained before maintenance.
Code @2: Call the internal method reserveAndGetWaitLength to calculate the waiting time.
Continue tracing the reserveAndGetWaitLength method.
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros); / / @ 1
return max(momentAvailable - nowMicros, 0); / / @ 2
}
Copy the code
Code @1: According to the current number of licenses and the current time, judge the earliest time that the license to be applied can be satisfied, represented by momentAvailable.
Code @2: Then calculate the difference between momentAvailable and nowMicros and compare it with 0 to get the waiting time.
Continue to track the reservewritten available method, an abstract method in RateLimiter, implemented in its subclass SmoothRateLimiter.
SmoothRateLimiter#reserveEarliestAvailable
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
resync(nowMicros); / / @ 1
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits); / / @ 2
double freshPermits = requiredPermits - storedPermitsToSpend; / / @ 3
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros); / / @ 4
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros); / / @ 5
this.storedPermits -= storedPermitsToSpend; / / @ 6
return returnValue;
}
Copy the code
Code @1: Before attempting to apply for a license, update storedPermits and nextFreeTicketMicros according to the current time i.e. license issuing rate (when the next license can be obtained for free).
Code @2: Calculate the number of licenses that can be consumed from storedPermitsToSpend this time, take the minimum value of the number of licenses that need to be applied and the number of currently available licenses, and express it with storedPermitsToSpend.
Code @3: If the number of requiredpermitsto be applied is greater than the number of storedpermitsto be applied, new permitsneed to be generated, denoted with Freshpermitsthat is, if the value is greater than 0, it means that the application needs to be blocked for a certain time.
Code @ 4: Calculate the waiting time for this application. The waiting time is composed of two parts, one is returned by the storedPermitsToWaitTime method, and the other part generates the required permissions at a steady rate. The time required is freshPermits * stableIntervalMicros. Later, we will analyze the implementation of storedPermitsToWaitTime method in detail.
Code @5: Update nextFreeTicketMicros to the current time plus the time to wait.
Code @6: Update storedPermits value, that is, reduce the number of permits consumed at this time.
Code @7: Note that the value of returnValue returned here does not include the time required to wait for the creation of a new license due to the remaining license, i.e., to allow a certain burst of traffic, so the wait time required by this calculation will take effect for the next request.
Let’s focus on SmoothWarmingUp’s storedPermitsToWaitTime method.
SmoothWarmingUp#SmoothWarmingUp
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { / / @ 1
double availablePermitsAboveThreshold = storedPermits - thresholdPermits; / / @ 2
long micros = 0;
if (availablePermitsAboveThreshold > 0.0) { / / @ 3
double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake); / / @ 31
// TODO(cpovirk): Figure out a good name for this variable.
double length = permitsToTime(availablePermitsAboveThreshold)
+ permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); / / @ 32
micros = (long) (permitsAboveThresholdToTake * length / 2.0); / / @ 33
permitsToTake -= permitsAboveThresholdToTake; / / @ 34
}
// measuring the integral on the left part of the function (the horizontal line)
micros += (stableIntervalMicros * permitsToTake); / / @ 4
return micros;
}
Copy the code
Code @1: Let’s start with the meaning of its two parameters:
- Double storedPermits Indicates the number of permissions currently stored.
- Double permitsToTake The number of licenses required for this application.
Code @ 2: AvailablePermitsAboveThreshold, currently beyond the license number of thresholdPermits, if more than thresholdPermits, permission is the result of more than part in the future, only after its limits, Thresholdpermitswill be applied for. See code @3 for the logic of this part.
@3: If the number of licenses currently stored exceeds a stable license thresholdpermit. i.e. there is an application logic for the number of prepended licenses, the key points of implementation are as follows:
- Get the number of licenses requested from the warm-up zone.
- The algorithm for obtaining a license time from the warm-up interval is somewhat obscure, and is implemented as @32~@34.
Code @4: Get a license time from stable interval, easy to understand, for fixed stableIntervalMicros.
Tips: The algorithm of obtaining multiple permitts from the calculation of preheating interval is related to Slope, and the author has not completed his perception, but at least we need to understand that when applying for a license from store permitts, priority consumption (greater than thresholdpermitts, Thresholdpermit-maxpermit.
So much for SmoothWarmingUp’s Acquire process.
4, summarize
SmoothWarmingUp acquire’s process is similar to SmoothBursty’s, so its flow chart is similar to the one below. The main difference is that the time to generate a license varies, mainly because it provides a warm-up mechanism.
If this article is helpful to you, please give it a thumbs up. Thanks.
Welcome to add the author micro signal (DINGwPMZ), add group discussion, the author quality column catalog: 1, source analysis RocketMQ column (40 +) 2, source analysis Sentinel column (12 +) 3, source analysis Dubbo column (28 +) 4, source analysis Mybatis column 5, source analysis Netty column (18 +) 6, source analysis JUC column Source code analysis (MyCat) for Elasticjob