In order to realize the functions of current limiting and fusing, the first problem to be solved is how to collect the service (resource) call information in real time. For example, if the current limiting width of an interface is set to 1W/ TPS, how to determine the current TPS first? Alibaba Sentinel uses sliding window to realize real-time data statistics.

Warm tip: if you are not interested in the source code, you can skip to the end of the article and take a look at the design schematic of the sliding window before deciding whether you need to read the source code.

This program recording

    • 1. Core class diagram of sliding window
    • 2. Implementation principle of sliding window
        • 2.1 ArrayMetric
        • 2.2 LongAdder
          • 2.2.1 Class diagram and core attributes
          • 2.2.2 currentWindow (), rounding
          • 2.2.3 isWindowDeprecated (), rounding
          • 2.2.4 getPreviousWindow (), rounding
          • 2.2.5 Sliding Window ICONS
    • 3, OccupiableBucketLeapArray explanation
        • 3.2 Constructors
        • 3.3 newEmptyBucket
        • 3.4 resetWindowTo
        • 3.5 addWaiting

1. Core class diagram of sliding window



Let’s first make a brief introduction to the core class above, focusing on the role and core attributes of the core class (focus on exploring its core data structure).

  • Metric metrics collect core interfaces that define the number of successes, exceptions, blocks, TPS, and response time in a sliding window.
  • ArrayMetric sliding window core implementation class.
  • LeapArray sliding window top-level data structure, containing window data one by one.
  • WindowWrap A wrapper class for each sliding window whose internal data structures are represented by MetricBucket.
  • MetricBucket Measures buckets, such as number of passes, number of blocks, number of exceptions, number of successes, and response time, that have passed the future quota (number of preempts for the next sliding window).
  • MetricEvent Indicator types, such as number of passes, number of blocks, number of exceptions, number of successes, and response time.

2. Implementation principle of sliding window

2.1 ArrayMetric

The entry class for the sliding window is ArrayMetric, and let’s take a look at its core code.

private final LeapArray<MetricBucket> data;   / / @ 1
public ArrayMetric(int sampleCount, int intervalInMs) {    / / @ 2
    this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
}
public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) {   / / @ 3
	if (enableOccupy) {
		this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
	} else {
		this.data = newBucketLeapArray(sampleCount, intervalInMs); }}Copy the code

Code @1: The unique property of the ArrayMetric class, used to store data for each window, which is the focus of our next exploration.

@2, @3 This class provides two constructors with core arguments:

  • Int intervalInMs indicates a collection interval, such as 1 second, 1 minute.
  • Int sampleCount Number of samples sampled in a collection interval. Default is 2. For example, when intervalInMs = 1000, a collection interval contains two equal intervals, one of which is the sliding window.
  • Boolean enableOccupy whether to allow preemption, namely the current timestamp already reach the limit, if you can take the next time the capacity of the window, corresponding LeapArray two implementation class here, if allowed to grab, OccupiableBucketLeapArray, Otherwise, BucketLeapArray.

Note that LeapArray’s generic class is MetricBucket, which stands for MetricBucket. You can think of a MetricBucket object as storing all metrics in a sampling time period, such as the number of passes, blocks, exceptions, successes, and response times in an abstract time period. The secret of its implementation is in LongAdder. This paper will not introduce this class in detail at first, and the subsequent articles will separately explore its implementation principle.

This time, instead of looking at the subclasses, let’s go the other way and look at the parent class.

2.2 LongAdder

2.2.1 Class diagram and core attributes



The core attributes of LeapArray are as follows:

  • Int windowLengthInMs Interval for each window, in milliseconds.
  • Int sampleCount number of samples, the number of sliding Windows contained in a statistical interval. In the same intervalInMs situation, the more sampleCount, the more accurate the sampled statistics, and the more memory required.
  • Int intervalInMs Indicates a statistical interval.
  • AtomicReferenceArray

    > array An array of sliding Windows at a statistical interval, where a sliding window is represented by a WindowWrap< MetricBucket >.

The meanings of the attributes above are derived from their constructors. See the constructors.

public LeapArray(int sampleCount, int intervalInMs) {
    AssertUtil.isTrue(sampleCount > 0."bucket count is invalid: " + sampleCount);
    AssertUtil.isTrue(intervalInMs > 0."total time interval of the sliding window should be positive");
    AssertUtil.isTrue(intervalInMs % sampleCount == 0."time span needs to be evenly divided");
    this.windowLengthInMs = intervalInMs / sampleCount;
    this.intervalInMs = intervalInMs;
    this.sampleCount = sampleCount;
    this.array = new AtomicReferenceArray<>(sampleCount);
}
Copy the code

Let’s move on to the LeapArray method and dive into the implementation details of sliding Windows.

2.2.2 currentWindow (), rounding

The method is mainly based on the current time to determine which sliding window, that is, to find the WindowWrap in the figure above, the method is to call its overloaded method, parameter is the system’s current time, so we focus on the implementation of overloaded method.

public WindowWrap<T> currentWindow(long timeMillis) { 
	if (timeMillis < 0) {
		return null;
	}
	int idx = calculateTimeIdx(timeMillis);  / / @ 1
	long windowStart = calculateWindowStart(timeMillis); / / @ 2
	while (true) { // An infinite loop is used to find the current time window. This loop is needed because multiple threads may be fetching the current time window.
		WindowWrap<T> old = array.get(idx);  / / @ 3
       		 if (old == null) {  / / @ 4
			WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
           		 if (array.compareAndSet(idx, null, window)) {  / / @ 5
				return window;
           		 } else{ Thread.yield(); }}else if (windowStart == old.windowStart()) { / / @ 6
			return old;
       		 } else if (windowStart > old.windowStart()) {  / / @ 7
			if (updateLock.tryLock()) {
               			 try {
					return resetWindowTo(old, windowStart);
                		} finally{ updateLock.unlock(); }}else{ Thread.yield(); }}else if (windowStart < old.windowStart()) { / / @ 8
            		return newWindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); }}}Copy the code

Code @1: Calculate the current time will fall in a collection interval (LeapArray) in which time window, i.e. in LeapArray property AtomicReferenceArray <WindowWrap< T>> array subscript. Its implementation algorithm is as follows:

  • First, divide the current time by the time interval of a time window to get the multiples of how many time Windows the current time is, denoted by n.
  • And then we can see that from a series of time Windows, starting at 0, we scroll forward n to get the position of the time window represented by the current timestamp. Now we want to locate the time window at the index of the array in LeapArray, and a LeapArray contains sampleCount elements. To get the index, use n % sampleCount.

Code @ 2: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap: WindowWrap The Sentinel algorithm is (timeMillis – timeMillis % windowLengthInMs).

Code @3: Attempts to find the specified subscript element from the WindowWrap array in LeapArray.

Code @4: Create a WindowWrap if the specified subscript element is empty. WindowWrap MetricBucket calls its abstract method newEmptyBucket (timeMillis), which is implemented by different subclasses.

Code @ 5: The CAS mechanism is used to update the elements in the LeapArray array. Because the same timestamp may have multiple threads fetching the current time window object, but the time window object has not been created yet, this is to avoid creating multiple threads, which will cause the statistics to be overwritten. Return to the newly created WindowWrap, CAS set unsuccessful threads to continue the process, and then enter code @6.

Code @6: Returns if the time window object under the specified index is not empty and determines that the start time is equal.

Code @7: If the pre-existing window start time is less than the start time calculated by the current timestamp, the bucket is deprecated. The start time needs to be reset to the corresponding start timestamp of the new timestamp, the logic of which is described in more detail below.

Code @8: Should not enter this branch, because the current time is calculated to be no smaller than the previous window.

2.2.3 isWindowDeprecated (), rounding

Next, let’s look at the window expiration mechanism.

public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap<T> windowWrap) {
    return isWindowDeprecated(TimeUtil.currentTimeMillis(), windowWrap);
}
public boolean isWindowDeprecated(long time, WindowWrap<T> windowWrap) {
	return time - windowWrap.windowStart() > intervalInMs;
}
Copy the code

The validity of the sliding window is judged by the fact that when the interval between the system time and the start timestamp of the sliding window is greater than one collection time, it means that the sliding window has expired. That is, starting from the current window, usually contains valid Windows for sampleCount valid sliding Windows.

2.2.4 getPreviousWindow (), rounding

Obtain the previous valid sliding window according to the current time, and its code is as follows:

public WindowWrap<T> getPreviousWindow(long timeMillis) {
    if (timeMillis < 0) {
		return null;
    }
    int idx = calculateTimeIdx(timeMillis - windowLengthInMs); / / @ 1
    timeMillis = timeMillis - windowLengthInMs;
    WindowWrap<T> wrap = array.get(idx);
    if (wrap == null || isWindowDeprecated(wrap)) {                 / / @ 2
		return null;
    }
   if (wrap.windowStart() + windowLengthInMs < (timeMillis)) {   / / @ 3
		return null;
    }
    return wrap;
}
Copy the code

The key point of implementation is as follows: code @1: Subtracts a window interval from the current time, and then locates the subscript of the array in LeapArray. Code @2: Returns NULL if null or expired. Code @3: If the location window starts at a time when windowLengthInMs is less than timeMills, then null is returned and it usually does not go to that branch.

2.2.5 Sliding Window ICONS

After the above analysis, we should be able to draw the implementation schematic of the sliding window implementation, although there is a core method (resetWindowTo) that has not been analyzed.



Next, a simple illustration of the above figure: The following example uses a sampling interval of 1 s and a sampling number of 2.

First, a LeapArray will be created, and an array with element 2 will be held internally. At the beginning of collection, the first and second subscripts of the array will be null. For example, the current time is located to subscript 0 through calculateTimeIdx, and there is no sliding window, a sliding window will be created. Then the sliding window will collect indicators, and a second sampling window will be created after 500ms of 1s.

Then the time will advance 1s, and it will locate at the subscript 0 again, but it will not be empty at this time, because the data collected in the last second will be discarded (MetricBucket value), and then reset the windowStart of the window. This is where the resetWindowTo method comes in.

Appeared in the ArrayMetric constructor LeapArray BucketLeapArray and OccupiableBucketLeapArray two implementations of the type.

Including BucketLeapArray is simpler, not in-depth study, here we will discuss the next OccupiableBucketLeapArray implementation principle, namely support preemption “token” in the future.

3, OccupiableBucketLeapArray explanation

So-called OccupiableBucketLeapArray, realizes the thought is the “token” in the current sample has run out, which meet the user setting of the related indicators of the rich value, can be a time window down, a sampling interval is borrowed from the future. Let’s explore its core implementation principles in detail.

3.1 class diagram



We focus on the OccupiableBucketLeapArray introduced a FutureBucketLeapArray member variables, its name is called borrowArray, namely to borrow.

3.2 Constructors

public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) {
    super(sampleCount, intervalInMs);
    this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs);
}
Copy the code

As can be seen from the constructor, not only is a regular LeapArray created, corresponding to a collection cycle, but also a borrowArray will be created, which will also contain a collection cycle.

3.3 newEmptyBucket

public MetricBucket newEmptyBucket(long time) {
	MetricBucket newBucket = new MetricBucket();   / / @ 1
	MetricBucket borrowBucket = borrowArray.getWindowValue(time);  / / @ 2
	if(borrowBucket ! =null) {  
		newBucket.reset(borrowBucket);  
	}
	return newBucket;
}
Copy the code

We know that newEmptyBucket is created when the current window is fetched and the corresponding array subscript is empty. Code @1: Start by creating a new MetricBucket. Code @2: During the creation, if there is a sliding window borrowed from the future, the data collected on the sliding window will be copied to the newly created collection index and then returned.

3.4 resetWindowTo

protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long time) {      
    w.resetTo(time);
    MetricBucket borrowBucket = borrowArray.getWindowValue(time);
    if(borrowBucket ! =null) {
        w.value().reset();
        w.value().addPass((int)borrowBucket.pass());
    } else {
        w.value().reset();
    }
    return w;
}
Copy the code

When a sliding window is out of date, it needs to be reset. This is the same idea as newEmptyBucket’s core idea: if there is a borrowed situation, it needs to be reset with a license that has been used in the future.

3.5 addWaiting

public void addWaiting(long time, int acquireCount) {
	WindowWrap<MetricBucket> window = borrowArray.currentWindow(time);
	window.value().add(MetricEvent.PASS, acquireCount);
}
Copy the code

After the above analysis, a bold guess should be that the “token” in the current sliding window has been used and borrowed from the future token. The proof will be given below.

The realization principle of sliding window is introduced here. You can follow the above code with the following figure to make an understanding.

There is, we can draw the OccupiableBucketLeapArray sliding window graphic. This part of the content will also be discussed in my [Middleware knowledge planet] with all the stars and friends, welcome to join.


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