The statistic node keep three kinds of real-time statistics metrics:
+ *
+ *
metrics in second level ({@code rollingCounterInSecond})
+ *
metrics in minute level ({@code rollingCounterInMinute})
+ *
thread count
+ *
+ *
+ *
+ * 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}.
+ *
+ *
+ *
+ * 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.
+ *
+ * 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.
+ *
+ *
* @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;
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java
index 30761e2f..39ee3c04 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java
@@ -32,10 +32,10 @@ import com.alibaba.csp.sentinel.util.TimeUtil;
* {@link #sampleCount} = intervalInMs / windowLengthInMs.
*
*
- * @param type of data bucket.
+ * @param type of statistic data
* @author jialiang.linjl
* @author Eric Zhao
- * @author CarpenterLee
+ * @author Carpenter Lee
*/
public abstract class LeapArray {
@@ -48,7 +48,8 @@ public abstract class LeapArray {
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 {
}
/**
- * 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 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 resetWindowTo(WindowWrap 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 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 old = array.get(idx);
if (old == null) {
- WindowWrap window = new WindowWrap(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 window = new WindowWrap(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(windowLengthInMs, time, newEmptyBucket());
+ } else if (windowStart < old.windowStart()) {
+ // Should not go through here, as the provided time is already behind.
+ return new WindowWrap(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 getPreviousWindow(long time) {
long timeId = (time - windowLengthInMs) / windowLengthInMs;
int idx = (int)(timeId % array.length());
@@ -146,10 +202,21 @@ public abstract class LeapArray {
return wrap;
}
+ /**
+ * Get the previous bucket item for current timestamp.
+ *
+ * @return the previous bucket item for current timestamp
+ */
public WindowWrap 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 {
return old.value();
}
- private boolean isWindowDeprecated(WindowWrap 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 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> list() {
List> result = new ArrayList>();
@@ -180,6 +260,12 @@ public abstract class LeapArray {
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 values() {
List result = new ArrayList();
@@ -192,4 +278,22 @@ public abstract class LeapArray {
}
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;
+ }
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java
index eb5c75ce..d59ca984 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java
@@ -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();
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java
index 5818e4ba..8edb2277 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java
@@ -25,24 +25,24 @@ package com.alibaba.csp.sentinel.slots.statistic.base;
public class WindowWrap {
/**
- * 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 {
this.value = value;
}
+ /**
+ * Reset start timestamp of current bucket to provided time.
+ *
+ * @param startTime valid start timestamp
+ * @return bucket after reset
+ */
public WindowWrap resetTo(long startTime) {
this.windowStart = startTime;
return this;
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
index f6a9277f..7a141a54 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
@@ -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.
*/
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
index 3a41e4e6..1b62057a 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
@@ -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 details();
/**
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java
index d5972b6d..fcf452e1 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java
@@ -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 {
/**
- * 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 {
@Override
protected WindowWrap resetWindowTo(WindowWrap w, long startTime) {
+ // Update the start time and reset value.
w.resetTo(startTime);
w.value().reset();
return w;
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/CacheMap.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/CacheMap.java
index f014f1e5..4039632b 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/CacheMap.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/CacheMap.java
@@ -41,5 +41,5 @@ public interface CacheMap {
void clear();
- Set ascendingKeySet();
+ Set keySet(boolean ascending);
}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/ConcurrentLinkedHashMapWrapper.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/ConcurrentLinkedHashMapWrapper.java
index cda53766..8b25e0c0 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/ConcurrentLinkedHashMapWrapper.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/cache/ConcurrentLinkedHashMapWrapper.java
@@ -86,7 +86,11 @@ public class ConcurrentLinkedHashMapWrapper implements CacheMap {
}
@Override
- public Set ascendingKeySet() {
- return map.ascendingKeySet();
+ public Set keySet(boolean ascending) {
+ if (ascending) {
+ return map.ascendingKeySet();
+ } else {
+ return map.descendingKeySet();
+ }
}
}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/ParamMapBucket.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/ParamMapBucket.java
index dba6fd5e..05ef13c8 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/ParamMapBucket.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/ParamMapBucket.java
@@ -66,7 +66,11 @@ public class ParamMapBucket {
}
public Set