3. 滑动窗口实现

 

                                                          滑动窗口类关系图(泛型和接口属性被置换为实体类)

 

滑动窗口核心类:

     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压力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值