本文基于:1.3.0-GA版本
这里截取官网的一张图:
Slot是从第一个往后一个个传递的,当数据到了StatisticSlot时,就开始进行统计了。
后面的所有的Slot都依赖这个统计进行校验。
StatisticSlot就是根据滑动窗口进行数据统计的,下面开始讲解。
一、包结构
如图:
红框内既是本文要分析的内容。
1、base包的内容主要就是滑动窗口的设计及多线程计数统计
2、metic包主要是指标的统计及指标数据的集合封装
3、StatisticSlot就是统计核心入口了,主要统计了正常情况、发生BlockException、其他异常的统计数据
4、StatisticSlotCallbackRegistry回调注册器,后续热点参数限流会涉及到
二、Metric及ArrayMetric
Metric
Metric是一个接口,用来统计各项指标的一个入口,如:success(成功数)、exception(异常数)、block(阻塞数)、pass(请求数)、rt(响应时间);并定义了这些指标的增加方法。
ArrayMetric
ArrayMetric是Metric的实现类,ArrayMetric实现了Metric定义的所有的方法;
注意ArrayMeric的构造方法,通过调用查询,可以发现StatisticNode类中定义的,如:
public class StatisticNode implements Node {
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT,
IntervalProperty.INTERVAL);
/**
* Holds statistics of the recent 60 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, 60);
//下面代码省略
}
/**
* 数据保存到地方
*/
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);
}
然后通过MetricsLeapArray的构造函数,构造滑动时间窗口:
这里通过注释的解释就能发现了
windowLengthInMs:一个单口滑动时间窗口的大小,单位是毫秒
intervalInSec:窗口的区间大小,单位秒
MetricsLeapArray又调用父类的LeapArray的构造方法进行构造。
三、LeapArray及WindowWrap
LeapArray
//单口滑动时间窗口的大小,单位是毫秒
protected int windowLengthInMs;
//滑动时间窗口个数
protected int sampleCount;
//统计时间区间
protected int intervalInMs;
/**
保存统计数据的地方,数组
*/
protected final AtomicReferenceArray<WindowWrap<T>> array;
//构造函数
public LeapArray(int windowLengthInMs, int intervalInSec) {
this.windowLengthInMs = windowLengthInMs;
//因为intervalInSec单位是秒,所以乘以1000
this.intervalInMs = intervalInSec * 1000;
this.sampleCount = intervalInMs / windowLengthInMs;
this.array = new AtomicReferenceArray<WindowWrap<T>>(sampleCount);
}
可以看到窗口的统计数据被被包装到WindowWrap了
WindowWrap
/**
* a single window bucket's time length in milliseconds.
*/
private final long windowLengthInMs;
/**
* Start time of the window in milliseconds.
*/
private long windowStart;
/**
* Statistic value.
*/
private T value;
WindowWrap是一个对象,真正的数据被保存到泛型T的value中,在这里使用的是MetricBucket对象。
MetricBucket
private final LongAdder pass = new LongAdder();
private final LongAdder block = new LongAdder();
private final LongAdder exception = new LongAdder();
private final LongAdder rt = new LongAdder();
private final LongAdder success = new LongAdder();
这里使用的LongAdder,LongAdder是JDK8新增的一个类,和AtomicLong类似,但是LongAdder支持在多线程下有更高的吞吐量。
四、调用链
通过分析上述关系,可以发现完整的流程如下:
具体分析
1、StatisticSolt是一个统计插槽,也是我们调用链入口:
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args)
throws Throwable {
try {
fireEntry(context, resourceWrapper, node, count, args);
node.increaseThreadNum();
//这里开始调用增加统计次数
node.addPassRequest();
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseThreadNum();
context.getCurEntry().getOriginNode().addPassRequest();
}
//以下代码省略
//......
}
2、通过DefaultNote调用父类StatisticNode的addPassRequest方法
3、StatisticNode创建了两个统计Metric:rollingCounterInSecond、rollingCounterInMinute,进行调用ArrayMetric的addPass方法
4、构建了时间窗口MetricsLeapArray,并调用:
@Override
public void addPass() {
WindowWrap<MetricBucket> wrap = data.currentWindow();
wrap.value().addPass();
}
从LeadArray数组中获取到当前时间的窗口包装器,
获取当前时间的时间窗口方法currentWindow,下一节详细介绍:https://www.jianshu.com/p/05677381e155
5、在该时间包装器对数据MetricBucket进行指标的操作
public void addPass() {
pass.add(1L);
}
五、总结
1、本文分析整个滑动时间窗口的构建过程及详细的调用链。
2、StatisticSlot是sentinel统计的核心,后续的流控,降级等都是根据此阶段的统计数据进行的。
3、本文目前还没有介绍滑动时间窗口的具体算法,请关注下一篇文章。
4、统计指标数据是根据LongAdder数据结构进行各项统计的,主要原理就是利用分段写入,减少竞争。