diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java index 12c85cd0..ad877822 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java @@ -16,8 +16,8 @@ package com.alibaba.csp.sentinel.node; import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; +import com.alibaba.csp.sentinel.property.SimplePropertyListener; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /*** @@ -25,36 +25,42 @@ import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; * * @author youji.zj * @author jialiang.linjl + * @author CarpenterLee */ public class IntervalProperty { + /** + *

+ * Interval in seconds. This variable determines sensitivity of the QPS calculation. + *

+ * DO NOT MODIFY this value directly, use {@link #updateInterval(int)}, otherwise the modification will not + * take effect. + */ public static volatile int INTERVAL = 1; - public static void init(SentinelProperty dataSource) { - dataSource.addListener(new FlowIntervalPropertyListener()); - } - - private static class FlowIntervalPropertyListener implements PropertyListener { - @Override - public void configUpdate(Integer value) { - if (value == null) { - value = 1; + public static void register2Property(SentinelProperty property) { + property.addListener(new SimplePropertyListener() { + @Override + public void configUpdate(Integer value) { + if (value != null) { + updateInterval(value); + } } - INTERVAL = value; - RecordLog.info("Init flow interval: " + INTERVAL); - } + }); + } - @Override - public void configLoad(Integer value) { - if (value == null) { - value = 1; - } - INTERVAL = value; - for (ClusterNode node : ClusterBuilderSlot.getClusterNodeMap().values()) { - node.reset(); - } - RecordLog.info("Flow interval change received: " + INTERVAL); + /** + * Update the {@link #INTERVAL}, All {@link ClusterNode}s will be reset if newInterval is + * different from {@link #INTERVAL} + * + * @param newInterval New interval to set. + */ + public static void updateInterval(int newInterval) { + if (newInterval != INTERVAL) { + INTERVAL = newInterval; + ClusterBuilderSlot.resetClusterNodes(); } + RecordLog.info("INTERVAL updated to: " + INTERVAL); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java index 427c45a7..ecd7b018 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java @@ -119,7 +119,8 @@ public interface Node { void decreaseThreadNum(); /** - * Reset the internal counter. + * Reset the internal counter. Reset is needed when {@link IntervalProperty#INTERVAL} or + * {@link SampleCountProperty#SAMPLE_COUNT} is changed. */ void reset(); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java index effc7ac4..8001a80e 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java @@ -21,31 +21,45 @@ import com.alibaba.csp.sentinel.property.SimplePropertyListener; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; /** + * Holds statistic buckets count per second. + * * @author jialiang.linjl + * @author CarpenterLee */ public class SampleCountProperty { - public static volatile int sampleCount = 2; - - public static void init(SentinelProperty property) { + /** + *

+ * Statistic buckets count per second. This variable determines sensitivity of the QPS calculation. + * DO NOT MODIFY this value directly, use {@link #updateSampleCount(int)}, otherwise the modification will not + * take effect. + *

+ * Node that this value must be divisor of 1000. + */ + public static volatile int SAMPLE_COUNT = 2; - try { - property.addListener(new SimplePropertyListener() { - @Override - public void configUpdate(Integer value) { - if (value != null) { - sampleCount = value; - // Reset the value. - for (ClusterNode node : ClusterBuilderSlot.getClusterNodeMap().values()) { - node.reset(); - } - } - RecordLog.info("Current SampleCount: " + sampleCount); + public static void register2Property(SentinelProperty property) { + property.addListener(new SimplePropertyListener() { + @Override + public void configUpdate(Integer value) { + if (value != null) { + updateSampleCount(value); } + } + }); + } - }); - } catch (Exception e) { - RecordLog.info(e.getMessage(), e); + /** + * Update the {@link #SAMPLE_COUNT}. All {@link ClusterNode}s will be reset if newSampleCount + * is different from {@link #SAMPLE_COUNT}. + * + * @param newSampleCount New sample count to set. This value must be divisor of 1000. + */ + public static void updateSampleCount(int newSampleCount) { + if (newSampleCount != SAMPLE_COUNT) { + SAMPLE_COUNT = newSampleCount; + ClusterBuilderSlot.resetClusterNodes(); } + RecordLog.info("SAMPLE_COUNT updated to: " + SAMPLE_COUNT); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java index a3e5418b..4158262b 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java @@ -31,7 +31,7 @@ import com.alibaba.csp.sentinel.slots.statistic.metric.Metric; */ public class StatisticNode implements Node { - private transient Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.sampleCount, + private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); /** @@ -70,7 +70,7 @@ public class StatisticNode implements Node { @Override public void reset() { - rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.sampleCount, IntervalProperty.INTERVAL); + rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); } @Override @@ -131,7 +131,7 @@ public class StatisticNode implements Node { @Override public long maxSuccessQps() { - return rollingCounterInSecond.maxSuccess() * SampleCountProperty.sampleCount; + return rollingCounterInSecond.maxSuccess() * SampleCountProperty.SAMPLE_COUNT; } @Override diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java index 9222a7b6..763bb951 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java @@ -36,7 +36,6 @@ public class MetricTimerListener implements Runnable { public void run() { Map> maps = new TreeMap>(); - // 每5秒打印一次,把丢弃的seconds都给丢掉。 for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { String name = e.getKey().getName(); ClusterNode node = e.getValue(); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java index 92940b46..9b851cfd 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java @@ -24,7 +24,9 @@ import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.DefaultNode; +import com.alibaba.csp.sentinel.node.IntervalProperty; import com.alibaba.csp.sentinel.node.Node; +import com.alibaba.csp.sentinel.node.SampleCountProperty; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; @@ -150,4 +152,13 @@ public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot return clusterNodeMap; } + /** + * Reset all {@link ClusterNode}s. Reset is needed when {@link IntervalProperty#INTERVAL} or + * {@link SampleCountProperty#SAMPLE_COUNT} is changed. + */ + public static void resetClusterNodes() { + for (ClusterNode node : clusterNodeMap.values()) { + node.reset(); + } + } } 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 f3469161..30761e2f 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 @@ -23,15 +23,23 @@ import java.util.concurrent.locks.ReentrantLock; import com.alibaba.csp.sentinel.util.TimeUtil; /** + *

* Basic data structure for statistic metrics. + *

+ *

+ * Using sliding window algorithm to count data. Each bucket cover {@link #windowLengthInMs} time span, + * and the total time span is {@link #intervalInMs}, so the total bucket count is: + * {@link #sampleCount} = intervalInMs / windowLengthInMs. + *

* - * @param type of data wrapper + * @param type of data bucket. * @author jialiang.linjl * @author Eric Zhao + * @author CarpenterLee */ public abstract class LeapArray { - protected int windowLength; + protected int windowLengthInMs; protected int sampleCount; protected int intervalInMs; @@ -39,10 +47,15 @@ public abstract class LeapArray { private final ReentrantLock updateLock = new ReentrantLock(); - public LeapArray(int windowLength, int intervalInSec) { - this.windowLength = windowLength; + /** + * The total bucket count is: {@link #sampleCount} = intervalInSec * 1000 / windowLengthInMs. + * @param windowLengthInMs a single window bucket's time length in milliseconds. + * @param intervalInSec the total time span of this {@link LeapArray} in seconds. + */ + public LeapArray(int windowLengthInMs, int intervalInSec) { + this.windowLengthInMs = windowLengthInMs; this.intervalInMs = intervalInSec * 1000; - this.sampleCount = intervalInMs / windowLength; + this.sampleCount = intervalInMs / windowLengthInMs; this.array = new AtomicReferenceArray>(sampleCount); } @@ -79,17 +92,17 @@ public abstract class LeapArray { * @return the window at provided timestamp */ public WindowWrap currentWindow(long time) { - long timeId = time / windowLength; + long timeId = time / windowLengthInMs; // Calculate current index. int idx = (int)(timeId % array.length()); // Cut the time to current window start. - time = time - time % windowLength; + time = time - time % windowLengthInMs; while (true) { WindowWrap old = array.get(idx); if (old == null) { - WindowWrap window = new WindowWrap(windowLength, time, newEmptyBucket()); + WindowWrap window = new WindowWrap(windowLengthInMs, time, newEmptyBucket()); if (array.compareAndSet(idx, null, window)) { return window; } else { @@ -111,22 +124,22 @@ public abstract class LeapArray { } else if (time < old.windowStart()) { // Cannot go through here. - return new WindowWrap(windowLength, time, newEmptyBucket()); + return new WindowWrap(windowLengthInMs, time, newEmptyBucket()); } } } public WindowWrap getPreviousWindow(long time) { - long timeId = (time - windowLength) / windowLength; + long timeId = (time - windowLengthInMs) / windowLengthInMs; int idx = (int)(timeId % array.length()); - time = time - windowLength; + time = time - windowLengthInMs; WindowWrap wrap = array.get(idx); if (wrap == null || isWindowDeprecated(wrap)) { return null; } - if (wrap.windowStart() + windowLength < (time)) { + if (wrap.windowStart() + windowLengthInMs < (time)) { return null; } @@ -138,7 +151,7 @@ public abstract class LeapArray { } public T getWindowValue(long time) { - long timeId = time / windowLength; + long timeId = time / windowLengthInMs; int idx = (int)(timeId % array.length()); WindowWrap old = array.get(idx); 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 e8529669..5818e4ba 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,9 +25,9 @@ package com.alibaba.csp.sentinel.slots.statistic.base; public class WindowWrap { /** - * The length of the window. + * a single window bucket's time length in milliseconds. */ - private final long windowLength; + private final long windowLengthInMs; /** * Start time of the window in milliseconds. @@ -40,18 +40,18 @@ public class WindowWrap { private T value; /** - * @param windowLength the time length of the window + * @param windowLengthInMs a single window bucket's time length in milliseconds. * @param windowStart the start timestamp of the window * @param value window data */ - public WindowWrap(long windowLength, long windowStart, T value) { - this.windowLength = windowLength; + public WindowWrap(long windowLengthInMs, long windowStart, T value) { + this.windowLengthInMs = windowLengthInMs; this.windowStart = windowStart; this.value = value; } public long windowLength() { - return windowLength; + return windowLengthInMs; } public long windowStart() { @@ -74,7 +74,7 @@ public class WindowWrap { @Override public String toString() { return "WindowWrap{" + - "windowLength=" + windowLength + + "windowLengthInMs=" + windowLengthInMs + ", windowStart=" + windowStart + ", value=" + value + '}'; 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 b7df1efa..684c8365 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 @@ -33,8 +33,14 @@ public class ArrayMetric implements Metric { private final MetricsLeapArray data; - public ArrayMetric(int windowLength, int interval) { - this.data = new MetricsLeapArray(windowLength, interval); + /** + * Constructor + * + * @param windowLengthInMs a single window bucket's time length in milliseconds. + * @param intervalInSec the total time span of this {@link ArrayMetric} in seconds. + */ + public ArrayMetric(int windowLengthInMs, int intervalInSec) { + this.data = new MetricsLeapArray(windowLengthInMs, intervalInSec); } /** 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 0c2dcb86..d5972b6d 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 @@ -22,11 +22,18 @@ import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; /** * The fundamental data structure for metric statistics in a time window. * + * @see LeapArray * @author jialiang.linjl * @author Eric Zhao */ 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. + */ public MetricsLeapArray(int windowLengthInMs, int intervalInSec) { super(windowLengthInMs, intervalInSec); }