滑动窗口类关系图(泛型和接口属性被置换为实体类)
滑动窗口核心类:
1. MetricBucket类
最底层的存储结构,统计了一个窗口时间内的各项指标数据,这些指标数据包括请求总数、成功总数、异常总数、总耗时、最小耗时、最大耗时等,而一个 Bucket 可以是记录一秒内的数据,也可以是 10 毫秒内的数据,这个时间长度称为窗口时间
public class MetricBucket {
/**
* 存储各事件的计数,比如异常总数、请求总数等
*/
private final LongAdder[] counters;
/**
* 这段事件内的最小耗时
*/
private volatile long minRt;
}
如上面代码所示,MetricBucket记录一段时间内的各项指标数据用的是一个 LongAdder 数组,LongAdder 保证了数据修改的原子性,并且性能比 AtomicInteger 表现更好。数组的每个元素分别记录一个时间窗口内的请求总数、异常数、总耗时,如下图所示。
Sentinel 用枚举类型 MetricEvent 的 ordinal 属性作为下标,ordinal 的值从 0 开始,按枚举元素的顺序递增,正好可以用作数组的下标。
// 事件类型
public enum MetricEvent {
EXCEPTION,// 异常 对应数组下标为 0
SUCCESS, // 成功 对应数组下标为 1
RT // 耗时 对应数组下标为 2
}
2.WindowWrap
public class WindowWrap {
/**
* 窗口时间长度(毫秒)
*/
private final long windowLengthInMs;
/**
* 开始时间戳(毫秒)
*/
private long windowStart;
/**
* 统计数据
*/
private MetricBucket value;
}
窗口包装类,windowLengthInMs定义了窗口时间长度,value是底层数据结构,windowStart定义了窗口的起始时间,当前时间根据统一的公式计算可确定将统计落入哪个窗口之中。
3. BucketLeapArray
//为了方便,将父类LeapArray中的属性挪用到子类中展示
public class BucketLeapArray {
/**
* 窗口大小 单位:毫秒
*/
protected int windowLengthInMs;
/**
* 窗口个数 即array数组的大小
*/
protected int sampleCount;
/**
* 窗口集合累加时长(intervalInMs = windowLengthInMs*sampleCount)
* 如60个窗口,每个窗口大小为1000ms,刚累加时长大小为 60*1000ms(1分钟)
*/
protected int intervalInMs;
/**
* 窗口集合
*/
protected final AtomicReferenceArray<WindowWrap<MetricBucket>> array;
/**
* 排他锁 (环形窗口数组,重复利用,下个统计周期会重置上个周期对应的窗口,重置时用lock保证线程安全)
*/
private final ReentrantLock updateLock = new ReentrantLock();
}
环形数组示意图
举个例子,我们需求统计1min内的数据,可以申请size=60的数组,每个元素代表1秒内的统计窗口。我们拿到当前的时间戳,1577017699235,去掉毫秒数得1577017699,对60取模得19,取数组中index=19的窗口,获取WindowWrap的windowStart,如果windowStart=1577017699000,取直接使用当前窗口统计数据,否则,说明此窗口已过期,重置WindowWrap对象的值,新窗口统计。
4.ArrayMetric
public class ArrayMetric implements Metric {
private final LeapArray<MetricBucket> data;
@Override
public long success() {
data.currentWindow();
long success = 0;
List<MetricBucket> list = data.values();
for (MetricBucket window : list) {
success += window.success();
}
return success;
}
@Override
public long maxSuccess() {
data.currentWindow();
long success = 0;
List<MetricBucket> list = data.values();
for (MetricBucket window : list) {
if (window.success() > success) {
success = window.success();
}
}
return Math.max(success, 1);
}
@Override
public long exception() {
data.currentWindow();
long exception = 0;
List<MetricBucket> list = data.values();
for (MetricBucket window : list) {
exception += window.exception();
}
return exception;
}
}
ArrayMetric类,实现了Metric方法,聚合了LeapArray类。里面的统计方法都是先找到具体的滑动窗口,然后再进行算术运算。
5. StatisticNode
Statistic 即统计的意思,StatisticNode 是 Node 接口的实现类,是实现实时指标数据统计 Node。
public class StatisticNode implements Node {
// 秒级滑动窗口,2 个时间窗口大小为 500 毫秒的 Bucket
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(2,1000);
// 分钟级滑动窗口,60 个 Bucket 数组,每个 Bucket 统计的时间窗口大小为 1 秒
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
// 统计并发使用的线程数
private LongAdder curThreadNum = new LongAdder();
}
StatisticNode对内封装了滑动窗口的具体逻辑,对外透明。StatisticNode的统计功能实际上转接到ArrayMetric中具体实现
借鉴: 1.MetricBucket类使用LongAdder来做计数统计,相较于AtomicLong等性能更高
2.滑动窗口使用数组(环形)重复利用WindowWrap,避免大量重复创建对象,减少ygc压力