Code and javadoc refinement
Signed-off-by: Eric Zhao <sczyh16@gmail.com>
This commit is contained in:
parent
c42ba34568
commit
f8bc6f631a
|
|
@ -26,11 +26,72 @@ import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric;
|
|||
import com.alibaba.csp.sentinel.slots.statistic.metric.Metric;
|
||||
|
||||
/**
|
||||
* <p>The statistic node keep three kinds of real-time statistics metrics:</p>
|
||||
* <ol>
|
||||
* <li>metrics in second level ({@code rollingCounterInSecond})</li>
|
||||
* <li>metrics in minute level ({@code rollingCounterInMinute})</li>
|
||||
* <li>thread count</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* Sentinel use sliding window to record and count the resource statistics in real-time.
|
||||
* The sliding window infrastructure behind the {@link ArrayMetric} is {@code LeapArray}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* case 1: When the first request comes in, Sentinel will create a new window bucket of
|
||||
* a specified time-span to store running statics, such as total response time(rt),
|
||||
* incoming request(QPS), block request(bq), etc. And the time-span is defined by sample count.
|
||||
* </p>
|
||||
* <pre>
|
||||
* 0 100ms
|
||||
* +-------+--→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
* <p>
|
||||
* Sentinel use the statics of the valid buckets to decide whether this request can be passed.
|
||||
* For example, if a rule defines that only 100 requests can be passed,
|
||||
* it will sum all qps in valid buckets, and compare it to the threshold defined in rule.
|
||||
* </p>
|
||||
*
|
||||
* <p>case 2: continuous requests</p>
|
||||
* <pre>
|
||||
* 0 100ms 200ms 300ms
|
||||
* +-------+-------+-------+-----→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
*
|
||||
* <p>case 3: requests keeps coming, and previous buckets become invalid</p>
|
||||
* <pre>
|
||||
* 0 100ms 200ms 800ms 900ms 1000ms 1300ms
|
||||
* +-------+-------+ ...... +-------+-------+ ...... +-------+-----→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
*
|
||||
* <p>The sliding window should become:</p>
|
||||
* <pre>
|
||||
* 300ms 800ms 900ms 1000ms 1300ms
|
||||
* + ...... +-------+ ...... +-------+-----→ Sliding Windows
|
||||
* ^
|
||||
* |
|
||||
* request
|
||||
* </pre>
|
||||
*
|
||||
* @author qinan.qn
|
||||
* @author jialiang.linjl
|
||||
*/
|
||||
public class StatisticNode implements Node {
|
||||
|
||||
/**
|
||||
* Holds statistics of the recent {@code INTERVAL} seconds. The {@code INTERVAL} is divided into time spans
|
||||
* by given {@code sampleCount}.
|
||||
*/
|
||||
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT,
|
||||
IntervalProperty.INTERVAL);
|
||||
|
||||
|
|
@ -40,6 +101,9 @@ public class StatisticNode implements Node {
|
|||
*/
|
||||
private transient Metric rollingCounterInMinute = new ArrayMetric(1000, 60);
|
||||
|
||||
/**
|
||||
* The counter for thread count.
|
||||
*/
|
||||
private AtomicInteger curThreadNum = new AtomicInteger(0);
|
||||
|
||||
private long lastFetchTime = -1;
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ import com.alibaba.csp.sentinel.util.TimeUtil;
|
|||
* {@link #sampleCount} = intervalInMs / windowLengthInMs.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> type of data bucket.
|
||||
* @param <T> type of statistic data
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @author CarpenterLee
|
||||
* @author Carpenter Lee
|
||||
*/
|
||||
public abstract class LeapArray<T> {
|
||||
|
||||
|
|
@ -48,7 +48,8 @@ public abstract class LeapArray<T> {
|
|||
private final ReentrantLock updateLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* The total bucket count is: {@link #sampleCount} = intervalInSec * 1000 / windowLengthInMs.
|
||||
* The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}.
|
||||
*
|
||||
* @param windowLengthInMs a single window bucket's time length in milliseconds.
|
||||
* @param intervalInSec the total time span of this {@link LeapArray} in seconds.
|
||||
*/
|
||||
|
|
@ -61,74 +62,129 @@ public abstract class LeapArray<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the window at current timestamp.
|
||||
* Get the bucket at current timestamp.
|
||||
*
|
||||
* @return the window at current timestamp
|
||||
* @return the bucket at current timestamp
|
||||
*/
|
||||
public WindowWrap<T> currentWindow() {
|
||||
return currentWindow(TimeUtil.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bucket.
|
||||
* Create a new statistic value for bucket.
|
||||
*
|
||||
* @return the new empty bucket
|
||||
*/
|
||||
public abstract T newEmptyBucket();
|
||||
|
||||
/**
|
||||
* Reset current window to provided start time and reset all counters.
|
||||
* Reset given bucket to provided start time and reset the value.
|
||||
*
|
||||
* @param startTime the start time of the window
|
||||
* @param windowWrap current window
|
||||
* @return new clean window wrap
|
||||
* @param startTime the start time of the bucket
|
||||
* @param windowWrap current bucket
|
||||
* @return new clean bucket at given start time
|
||||
*/
|
||||
protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime);
|
||||
|
||||
/**
|
||||
* Get window at provided timestamp.
|
||||
* Get bucket item at provided timestamp.
|
||||
*
|
||||
* @param time a valid timestamp
|
||||
* @return the window at provided timestamp
|
||||
* @return current bucket item at provided timestamp
|
||||
*/
|
||||
public WindowWrap<T> currentWindow(long time) {
|
||||
long timeId = time / windowLengthInMs;
|
||||
// Calculate current index.
|
||||
// Calculate current index so we can map the timestamp to the leap array.
|
||||
int idx = (int)(timeId % array.length());
|
||||
|
||||
// Cut the time to current window start.
|
||||
time = time - time % windowLengthInMs;
|
||||
// Calculate current bucket start time.
|
||||
long windowStart = time - time % windowLengthInMs;
|
||||
|
||||
/*
|
||||
* Get bucket item at given time from the array.
|
||||
*
|
||||
* (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
|
||||
* (2) Bucket is up-to-date, then just return the bucket.
|
||||
* (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets.
|
||||
*/
|
||||
while (true) {
|
||||
WindowWrap<T> old = array.get(idx);
|
||||
if (old == null) {
|
||||
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, time, newEmptyBucket());
|
||||
/*
|
||||
* B0 B1 B2 NULL B4
|
||||
* ||_______|_______|_______|_______|_______||___
|
||||
* 200 400 600 800 1000 1200 timestamp
|
||||
* ^
|
||||
* time=888
|
||||
* bucket is empty, so create new and update
|
||||
*
|
||||
* If the old bucket is absent, then we create a new bucket at {@code windowStart},
|
||||
* then try to update circular array via a CAS operation. Only one thread can
|
||||
* succeed to update, while other threads yield its time slice.
|
||||
*/
|
||||
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket());
|
||||
if (array.compareAndSet(idx, null, window)) {
|
||||
// Successfully updated, return the created bucket.
|
||||
return window;
|
||||
} else {
|
||||
// Contention failed, the thread will yield its time slice to wait for bucket available.
|
||||
Thread.yield();
|
||||
}
|
||||
} else if (time == old.windowStart()) {
|
||||
} else if (windowStart == old.windowStart()) {
|
||||
/*
|
||||
* B0 B1 B2 B3 B4
|
||||
* ||_______|_______|_______|_______|_______||___
|
||||
* 200 400 600 800 1000 1200 timestamp
|
||||
* ^
|
||||
* time=888
|
||||
* startTime of Bucket 3: 800, so it's up-to-date
|
||||
*
|
||||
* If current {@code windowStart} is equal to the start timestamp of old bucket,
|
||||
* that means the time is within the bucket, so directly return the bucket.
|
||||
*/
|
||||
return old;
|
||||
} else if (time > old.windowStart()) {
|
||||
} else if (windowStart > old.windowStart()) {
|
||||
/*
|
||||
* (old)
|
||||
* B0 B1 B2 NULL B4
|
||||
* |_______||_______|_______|_______|_______|_______||___
|
||||
* ... 1200 1400 1600 1800 2000 2200 timestamp
|
||||
* ^
|
||||
* time=1676
|
||||
* startTime of Bucket 2: 400, deprecated, should be reset
|
||||
*
|
||||
* If the start timestamp of old bucket is behind provided time, that means
|
||||
* the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.
|
||||
* Note that the reset and clean-up operations are hard to be atomic,
|
||||
* so we need a update lock to guarantee the correctness of bucket update.
|
||||
*
|
||||
* The update lock is fine-grained and will take effect only when
|
||||
* bucket is deprecated, so in most cases it won't lead to performance loss.
|
||||
*/
|
||||
if (updateLock.tryLock()) {
|
||||
try {
|
||||
// if (old is deprecated) then [LOCK] resetTo currentTime.
|
||||
return resetWindowTo(old, time);
|
||||
// Successfully get the update lock, now we reset the bucket.
|
||||
return resetWindowTo(old, windowStart);
|
||||
} finally {
|
||||
updateLock.unlock();
|
||||
}
|
||||
} else {
|
||||
// Contention failed, the thread will yield its time slice to wait for bucket available.
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
} else if (time < old.windowStart()) {
|
||||
// Cannot go through here.
|
||||
return new WindowWrap<T>(windowLengthInMs, time, newEmptyBucket());
|
||||
} else if (windowStart < old.windowStart()) {
|
||||
// Should not go through here, as the provided time is already behind.
|
||||
return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous bucket item before provided timestamp.
|
||||
*
|
||||
* @param time a valid timestamp
|
||||
* @return the previous bucket item before provided timestamp
|
||||
*/
|
||||
public WindowWrap<T> getPreviousWindow(long time) {
|
||||
long timeId = (time - windowLengthInMs) / windowLengthInMs;
|
||||
int idx = (int)(timeId % array.length());
|
||||
|
|
@ -146,10 +202,21 @@ public abstract class LeapArray<T> {
|
|||
return wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous bucket item for current timestamp.
|
||||
*
|
||||
* @return the previous bucket item for current timestamp
|
||||
*/
|
||||
public WindowWrap<T> getPreviousWindow() {
|
||||
return getPreviousWindow(System.currentTimeMillis());
|
||||
return getPreviousWindow(TimeUtil.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistic value from bucket for provided timestamp.
|
||||
*
|
||||
* @param time a valid timestamp
|
||||
* @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null
|
||||
*/
|
||||
public T getWindowValue(long time) {
|
||||
long timeId = time / windowLengthInMs;
|
||||
int idx = (int)(timeId % array.length());
|
||||
|
|
@ -162,10 +229,23 @@ public abstract class LeapArray<T> {
|
|||
return old.value();
|
||||
}
|
||||
|
||||
private boolean isWindowDeprecated(WindowWrap<T> windowWrap) {
|
||||
/**
|
||||
* Check if a bucket is deprecated, which means that the bucket
|
||||
* has been behind for at least an entire window time span.
|
||||
*
|
||||
* @param windowWrap a non-null bucket
|
||||
* @return true if the bucket is deprecated; otherwise false
|
||||
*/
|
||||
private boolean isWindowDeprecated(/*@NonNull*/ WindowWrap<T> windowWrap) {
|
||||
return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid bucket list for entire sliding window.
|
||||
* The list will only contain "valid" buckets.
|
||||
*
|
||||
* @return valid bucket list for entire sliding window.
|
||||
*/
|
||||
public List<WindowWrap<T>> list() {
|
||||
List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>();
|
||||
|
||||
|
|
@ -180,6 +260,12 @@ public abstract class LeapArray<T> {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aggregated value list for entire sliding window.
|
||||
* The list will only contain value from "valid" buckets.
|
||||
*
|
||||
* @return aggregated value list for entire sliding window
|
||||
*/
|
||||
public List<T> values() {
|
||||
List<T> result = new ArrayList<T>();
|
||||
|
||||
|
|
@ -192,4 +278,22 @@ public abstract class LeapArray<T> {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sample count (total amount of buckets).
|
||||
*
|
||||
* @return sample count
|
||||
*/
|
||||
public int getSampleCount() {
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total interval length of the sliding window.
|
||||
*
|
||||
* @return interval in second
|
||||
*/
|
||||
public int getIntervalInSecond() {
|
||||
return intervalInMs / 1000;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package com.alibaba.csp.sentinel.slots.statistic.base;
|
|||
import com.alibaba.csp.sentinel.Constants;
|
||||
|
||||
/**
|
||||
* Represents metrics data in a period of time window.
|
||||
* Represents metrics data in a period of time span.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
|
|
@ -42,9 +42,9 @@ public class MetricBucket {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clean the adders and reset window to provided start time.
|
||||
* Reset the adders.
|
||||
*
|
||||
* @return new clean window
|
||||
* @return new metric bucket in initial state
|
||||
*/
|
||||
public MetricBucket reset() {
|
||||
pass.reset();
|
||||
|
|
|
|||
|
|
@ -25,24 +25,24 @@ package com.alibaba.csp.sentinel.slots.statistic.base;
|
|||
public class WindowWrap<T> {
|
||||
|
||||
/**
|
||||
* a single window bucket's time length in milliseconds.
|
||||
* Time length of a single window bucket in milliseconds.
|
||||
*/
|
||||
private final long windowLengthInMs;
|
||||
|
||||
/**
|
||||
* Start time of the window in milliseconds.
|
||||
* Start timestamp of the window in milliseconds.
|
||||
*/
|
||||
private long windowStart;
|
||||
|
||||
/**
|
||||
* Statistic value.
|
||||
* Statistic data.
|
||||
*/
|
||||
private T value;
|
||||
|
||||
/**
|
||||
* @param windowLengthInMs a single window bucket's time length in milliseconds.
|
||||
* @param windowStart the start timestamp of the window
|
||||
* @param value window data
|
||||
* @param windowStart the start timestamp of the window
|
||||
* @param value statistic data
|
||||
*/
|
||||
public WindowWrap(long windowLengthInMs, long windowStart, T value) {
|
||||
this.windowLengthInMs = windowLengthInMs;
|
||||
|
|
@ -66,6 +66,12 @@ public class WindowWrap<T> {
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset start timestamp of current bucket to provided time.
|
||||
*
|
||||
* @param startTime valid start timestamp
|
||||
* @return bucket after reset
|
||||
*/
|
||||
public WindowWrap<T> resetTo(long startTime) {
|
||||
this.windowStart = startTime;
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ public class ArrayMetric implements Metric {
|
|||
private final MetricsLeapArray data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param windowLengthInMs a single window bucket's time length in milliseconds.
|
||||
* @param intervalInSec the total time span of this {@link ArrayMetric} in seconds.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ public interface Metric {
|
|||
*/
|
||||
long success();
|
||||
|
||||
/**
|
||||
* Get max success count.
|
||||
*
|
||||
* @return max success count
|
||||
*/
|
||||
long maxSuccess();
|
||||
|
||||
/**
|
||||
|
|
@ -59,7 +64,7 @@ public interface Metric {
|
|||
long pass();
|
||||
|
||||
/**
|
||||
* Get total RT.
|
||||
* Get total response time.
|
||||
*
|
||||
* @return total RT
|
||||
*/
|
||||
|
|
@ -72,6 +77,11 @@ public interface Metric {
|
|||
*/
|
||||
long minRt();
|
||||
|
||||
/**
|
||||
* Get aggregated metric nodes of all resources.
|
||||
*
|
||||
* @return metric node list of all resources
|
||||
*/
|
||||
List<MetricNode> details();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket;
|
|||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||
|
||||
/**
|
||||
* The fundamental data structure for metric statistics in a time window.
|
||||
* The fundamental data structure for metric statistics in a time span.
|
||||
*
|
||||
* @see LeapArray
|
||||
* @author jialiang.linjl
|
||||
|
|
@ -29,8 +29,6 @@ import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
|||
public class MetricsLeapArray extends LeapArray<MetricBucket> {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param windowLengthInMs a single window bucket's time length in milliseconds.
|
||||
* @param intervalInSec the total time span of this {@link MetricsLeapArray} in seconds.
|
||||
*/
|
||||
|
|
@ -45,6 +43,7 @@ public class MetricsLeapArray extends LeapArray<MetricBucket> {
|
|||
|
||||
@Override
|
||||
protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long startTime) {
|
||||
// Update the start time and reset value.
|
||||
w.resetTo(startTime);
|
||||
w.value().reset();
|
||||
return w;
|
||||
|
|
|
|||
|
|
@ -41,5 +41,5 @@ public interface CacheMap<K, V> {
|
|||
|
||||
void clear();
|
||||
|
||||
Set<K> ascendingKeySet();
|
||||
Set<K> keySet(boolean ascending);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,11 @@ public class ConcurrentLinkedHashMapWrapper<T, R> implements CacheMap<T, R> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<T> ascendingKeySet() {
|
||||
return map.ascendingKeySet();
|
||||
public Set<T> keySet(boolean ascending) {
|
||||
if (ascending) {
|
||||
return map.ascendingKeySet();
|
||||
} else {
|
||||
return map.descendingKeySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,11 @@ public class ParamMapBucket {
|
|||
}
|
||||
|
||||
public Set<Object> ascendingKeySet(RollingParamEvent type) {
|
||||
return data[type.ordinal()].ascendingKeySet();
|
||||
return data[type.ordinal()].keySet(true);
|
||||
}
|
||||
|
||||
public Set<Object> descendingKeySet(RollingParamEvent type) {
|
||||
return data[type.ordinal()].keySet(false);
|
||||
}
|
||||
|
||||
public static final int DEFAULT_MAX_CAPACITY = 200;
|
||||
|
|
|
|||
|
|
@ -60,10 +60,24 @@ public class HotParameterLeapArray extends LeapArray<ParamMapBucket> {
|
|||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event count for specific parameter value.
|
||||
*
|
||||
* @param event target event
|
||||
* @param count count to add
|
||||
* @param value parameter value
|
||||
*/
|
||||
public void addValue(RollingParamEvent event, int count, Object value) {
|
||||
currentWindow().value().add(event, count, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "top-N" value-QPS map of provided event.
|
||||
*
|
||||
* @param event target event
|
||||
* @param number max number of values
|
||||
* @return "top-N" value map
|
||||
*/
|
||||
public Map<Object, Double> getTopValues(RollingParamEvent event, int number) {
|
||||
currentWindow();
|
||||
List<ParamMapBucket> buckets = this.values();
|
||||
|
|
|
|||
Loading…
Reference in New Issue