sentinel源码分析-08流量数据统计

流量数据统计

上面我说了各种流控规则如何进行限流,那这个流量数据是如何统计出来的呢?这里我们回到StatisticNode

//每秒统计数据
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(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(60, 60 * 1000, false);

/**
 * The counter for thread count.
 */
//线程总数
private AtomicInteger curThreadNum = new AtomicInteger(0);

我们可以看到统计节点主要是保存三种实时统计指标,每秒统计信息、每分钟的统计信息、并发线程数

Metric接口定义了获取各种统计信息的方法,如

long success();
long block();
long exception();
long pass();
long rt();
......

ArrayMetric底层又是通过LeapArray来完成数据的统计,其中几个重要的属性

protected int windowLengthInMs;//每个窗口时间长度
protected int sampleCount;//时间窗口个数
protected int intervalInMs;//总的间隔时间
protected final AtomicReferenceArray<WindowWrap<T>> array;//存放时间窗口的集合

 public LeapArray(int sampleCount, int intervalInMs) {
        //每个窗口时间长度
        this.windowLengthInMs = intervalInMs / sampleCount;
        //总的间隔时间
        this.intervalInMs = intervalInMs;
        //窗口个数
        this.sampleCount = sampleCount;
        //存放窗口类
        this.array = new AtomicReferenceArray<>(sampleCount);
    }

这里就是将时间按照窗口数量化分成相同大小的环形桶,当前请求过来时,根据请求的时间查看落在那个时间窗口内,这里我们以成功通过后的数据统计为入口

 

public WindowWrap<T> currentWindow(long timeMillis) {
    //当前时间不能小于0
    if (timeMillis < 0) {
        return null;
    }
    //计算当前时间的索引
    int idx = calculateTimeIdx(timeMillis);
    // Calculate current bucket start time.
    //时间窗的起始时间
    long windowStart = calculateWindowStart(timeMillis);
    while (true) {
        WindowWrap<T> old = array.get(idx);
        if (old == null) {
            //之前的窗口不存在就创建一个新的
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            if (array.compareAndSet(idx, null, window)) {//CSA尝试将这个位置的窗口替换
                //替换成功,返回新创建的窗口
                return window;
            } else {
                //更新失败,说明有其他线程在竞争,让出执行权限
                Thread.yield();
            }
        } else if (windowStart == old.windowStart()) {
            //时间窗已创建而且没有过期
            return old;
        } else if (windowStart > old.windowStart()) {
            //时间窗已经过期,需要重置时间窗
            if (updateLock.tryLock()) {
                try {
                    return resetWindowTo(old, windowStart);
                } finally {
                    updateLock.unlock();
                }
            } else {

                Thread.yield();
            }
        } else if (windowStart < old.windowStart()) {
            //除非修改时间否则不会发生
            return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
        }
    }
}

这里array可以理解就是一个环形的桶,首先计算当前时间落在那个桶中,及计算索引值

private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
    long timeId = timeMillis / windowLengthInMs;
    // Calculate current index so we can map the timestamp to the leap array.
    return (int)(timeId % array.length());//计算当前时间在时间窗口数组中的索引
}

然后根据当前时间计算这个桶对应的起始值

//求出所在窗口的起始时间
protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
    return timeMillis - timeMillis % windowLengthInMs;
}

然后根据索引值idx和windowStart计算有几种结果

  • 索引idx位置的时间窗口还没有创建,则说明是这个窗口内的第一次请求,则需要新创建一个时间窗
  • 索引idx位置的时间窗已经创建
    • 如果windowStart > oldwindowStart:说明idx处的时间窗口已经过期是一个无效的时间窗口,则需要重置这个时间窗口windowStart和统计数据为初始值就可以继续使用了
    • 如果windowStart < oldwindowStart:不可能发生,除非修改了时间
    • 如果windowStart = oldwindowStart:说明我们本次请求的时间刚好和idx位置的时间窗口匹配,则返回这个时间窗

时间窗定义的重要属性

private final long windowLengthInMs;//时间窗口长度
private long windowStart;//窗口起始时间
private T value;//统计数据

那每个窗口内的数据是怎么统计的呢?其实是通过MetricBucket类

private final LongAdder[] counters;

private volatile long minRt;//最小RT时间

public MetricBucket() {
    //流控结果
    MetricEvent[] events = MetricEvent.values();
    //计数器
    this.counters = new LongAdder[events.length];
    for (MetricEvent event : events) {
        counters[event.ordinal()] = new LongAdder();
    }
    initMinRt();
}

这里统计的维度定义就在MetricEvent

public enum MetricEvent {

    /**
     * Normal pass.
     */
    PASS,
    /**
     * Normal block.
     */
    BLOCK,
    EXCEPTION,
    SUCCESS,
    RT,

    /**
     * Passed in future quota (pre-occupied, since 1.5.0).
     */
    OCCUPIED_PASS
}

获取所有的MetricEvent,然后创建LongAdder计数器数组,并进行初始化同事对minRt进行初始化。最终所有统计数据其实都是通过LongAdder来进行计数,这个类在高并发下有更高的性能。

我们看下如何获取统计数据

@Override
public long success() {
    data.currentWindow();
    long success = 0;
	//获取有效结果的时间窗
    List<MetricBucket> list = data.values();
    for (MetricBucket window : list) {
        success += window.success();
    }
    return success;
}

public List<T> values() {
        return values(TimeUtil.currentTimeMillis());
    }

    public List<T> values(long timeMillis) {
        if (timeMillis < 0) {
            return new ArrayList<T>();
        }
        //数组数量
        int size = array.length();
        List<T> result = new ArrayList<T>(size);

        for (int i = 0; i < size; i++) {
            WindowWrap<T> windowWrap = array.get(i);
            //如果窗口没有创建或不属于当前统计时间范围则跳过
            if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) {
                continue;
            }
            result.add(windowWrap.value());
        }
        return result;
    }

这里通过当前时间获取有效结果的时间窗集合,然后遍历所有时间窗集合然后或的成功的请求总数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值