前言
这一篇是上一篇的继续,如果不了解Sentinel ,请先阅读[Alibaba Sentinel 源码阅读(Part1 执行流程)](Alibaba Sentinel 源码阅读(Part1 执行流程))
入口
在上一篇我们看到 我们获取的所有信息,都是从StatisticNode
的这两个数据结构中获取的
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT,
IntervalProperty.INTERVAL);
/**
* Holds statistics of the recent 120 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds,
* meaning each bucket per second, in this way we can get accurate statistics of each second.
*/
private transient Metric rollingCounterInMinute = new ArrayMetric(1000, 2 * 60);
rollingCounterInMinute
这个两分钟之内的每一秒中数据的一个list,而每一秒中的数据是存储在 MetricBucket
,
ArrayMetric
// ArrayMetric 实现了Metric 接口,同时包含了 MetricsLeapArray数据结构,接口的实现就是通过这个MetricsLeapArray来实现的
// MetricsLeapArray 是从 LeapArray 继承的,所以这一篇的重点就是LeapArray了
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.
*/
public ArrayMetric(int windowLengthInMs, int intervalInSec) {
this.data = new MetricsLeapArray(windowLengthInMs, intervalInSec);
}
}
LeapArray
实际上就是一个环形数组,来给张官方的图就明白了
看文档其实很清晰,整个是基于时间窗口滑动算法来实现的
新增当前统计数据
@Override
public void addSuccess() {
WindowWrap<MetricBucket> wrap = data.currentWindow();
wrap.value().addSuccess();
}
获取时间窗口内统计数据
@Override
public long success() {
data.currentWindow();
long success = 0;
List<MetricBucket> list = data.values();
for (MetricBucket window : list) {
success += window.success();
}
return success;
}
所以重点的方法就是 data.currentWindow()方法了
protected final AtomicReferenceArray<WindowWrap<T>> array;
public LeapArray(int windowLengthInMs, int intervalInSec) {
this.windowLengthInMs = windowLengthInMs;
this.intervalInMs = intervalInSec * 1000;
this.sampleCount = intervalInMs / windowLengthInMs;
// 初始化容量大小
this.array = new AtomicReferenceArray<WindowWrap<T>>(sampleCount);
}
/**
* Get window at provided timestamp.
*
* @param time a valid timestamp
* @return the window at provided timestamp
*/
public WindowWrap<T> currentWindow(long time) {
long timeId = time / windowLengthInMs;
// Calculate current index.
int idx = (int)(timeId % array.length());
// Cut the time to current window start.
time = time - time % windowLengthInMs;
while (true) {
WindowWrap<T> old = array.get(idx);
if (old == null) {
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, time, newEmptyBucket());
if (array.compareAndSet(idx, null, window)) {
return window;
} else {
Thread.yield();
}
} else if (time == old.windowStart()) {
return old;
} else if (time > old.windowStart()) {
if (updateLock.tryLock()) {
try {
// if (old is deprecated) then [LOCK] resetTo currentTime.
return resetWindowTo(old, time);
} finally {
updateLock.unlock();
}
} else {
Thread.yield();
}
} else if (time < old.windowStart()) {
// Cannot go through here.
return new WindowWrap<T>(windowLengthInMs, time, newEmptyBucket());
}
}
}
这部分的内容会维持一个有效的环形数组以统计数据,具体要自己debug 看了。
总结
这里也只是把大致流程梳理了一下方便大家看源码而已,很多地方没有具体分析,这部分还是需要自己亲力亲为。