Sentinel-StatisticSlot滑动窗口统计运行原理

StatisticSlot是sentinel责任链中的一个,作用是从多个维度(入口流量、调用者、当前被访问资源)统计响应时间、并发线程数、处理失败个数、处理成功个数等,内部采用了滑动窗口算法。本文将详细介绍滑动窗口算法原理以及StatisticSlot如何实现。

1、滑动窗口算法

滑动窗口算法其实更多的描述的是一种思想,它在内部建立若干个窗口,根据要求将数据落入到不同的窗口中,这样我们可以根据窗口对数据做出统计或计算。根据不同的场景,窗口可以有不同的含义,比如局限于StatisticSlot中,窗口大小表示时间间隔,在一些场景中,窗口大小还可以是字符串长度。
下面介绍一下滑动窗口算法在StatisticSlot中是如何应用的。
StatisticSlot内部建立多个窗口,默认是2个,每个窗口大小都是固定的,窗口大小表示时间间隔。当外部访问资源时,根据访问时间将请求落入对应的窗口内。这样便可以计算最近一段时间总的请求数,QPS等数据。随着窗口的滑动,过期的窗口被剔除,然后创建新的窗口。
下面图片来资源sentinel官网:
在这里插入图片描述
图上创建了5个窗口,每个窗口大小是200ms,第一个图的窗口起始时间是200ms,当前时间是910ms,那么此时请求会落入第四个窗口中(800ms~1000ms)。时间走到1001ms时,200ms~400ms的窗口过期,窗口向右滑动,新加入一个1200ms~1400ms的窗口,之后新请求会落入这个新加入的窗口中。

2、StatisticSlot实现原理解析

在StatisticSlot中,使用WindowWrap对象表示窗口。WindowWrap有三个属性:

  • windowLengthInMs:窗口大小,单位是毫秒;
  • windowStart:窗口的起始时间;
  • value:存储时间窗口内的统计数据,比如窗口内的请求数等,该属性使用了泛型。

这里需要重点介绍一下WindowWrap的value属性,该属性使用了泛型,因此可以表示不同类的对象。在StatisticSlot中,value表示的是MetricBucket对象。MetricBucket可以存储6中统计数据,分别是:

  • 异常数(EXCEPTION,被访问的资源抛出异常或者通过Tracer记录的异常)
  • 成功数(SUCCESS)
  • 响应时间(RT)
  • 接收请求数(PASS,与SUCCESS的区别是,SUCCESS是资源已经访问完毕,而PASS仅仅是收到请求后允许访问资源但是还未访问,还没有开始访问资源)
  • 阻塞请求数(BLOCK,因为不符合流控规则或者服务被降级等原因而禁止访问资源)
  • 占用未来的访问请求数(OCCUPIED_PASS,如果超过了FlowRule规定的单位时间内最大请求数或线程数,会被阻止访问资源,但是允许具有优先级的请求占用未来的访问请求数,对于这种请求,sentinel会先占用未来时间窗口的令牌,然后阻塞线程一段时间,之后放行线程访问资源)。

上面每种统计数据都有对应的枚举值,这些值在类MetricEvent中定义。
MetricBucket使用LongAdder的数组记录这些统计数据:

    private final LongAdder[] counters;

时间窗口WindowWrap介绍完了,还缺少一个管理WindowWrap对象的类。sentinel使用LeapArray管理这些对象。LeapArray将这些对象组织成一个数组array:

//protected final AtomicReferenceArray<WindowWrap<T>> array;
//sampleCount表示时间窗口的个数
this.array = new AtomicReferenceArray<>(sampleCount);

在LeapArray中提供了获取时间窗口的方法currentWindow():

	//入参一般是当前系统时间,也就是线程访问资源的时间,
	//currentWindow()方法的作用是根据访问资源的时间获取对应的时间窗口
    public WindowWrap<T> currentWindow(long timeMillis) {
        if (timeMillis < 0) {
            return null;
        }
		//计算当前时间对应的时间窗口对象在array数组中的下标
        int idx = calculateTimeIdx(timeMillis);
        //计算时间窗口的起始时间
        long windowStart = calculateWindowStart(timeMillis);
  
        while (true) {
        	//根据下标获取时间窗口对象
            WindowWrap<T> old = array.get(idx);
            if (old == null) {
            	//如果时间窗口对象是null,表示该请求是落入时间窗口里面的第一个请求,时间窗口对象还未创建,
            	//newEmptyBucket()方法用于创建MetricBucket对象
                WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
                //将新建的时间窗口对象设置到array数组中
                if (array.compareAndSet(idx, null, window)) {
                    return window;
                } else {
                    Thread.yield();
                }
            } else if (windowStart == old.windowStart()) {
            	//当前请求时间对应的时间窗口在array数组中存在,直接返回
                return old;
            } else if (windowStart > old.windowStart()) {
            	//当前请求时间对应的时间窗口起始时间大于array里面的时间,
            	//意味着array数组里面的时间窗口已经过期,需要将该时间窗口清理掉,换上新的时间窗口
                if (updateLock.tryLock()) {
                    try {
                    	//resetWindowTo()方法由子类实现
                        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;
        return (int)(timeId % array.length());
    }
    //计算时间窗口的起始时间
    protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
        return timeMillis - timeMillis % windowLengthInMs;
    }

currentWindow()方法返回时间窗口后,sentinel接下来更新MetricBucket里面的统计值或者根据统计值计算QPS。

LeapArray使用了“懒策略”在需要的时候才创建窗口对象,窗口对象过期了也不会实时检查,而是请求过来的时候,才去检查对应的窗口。另外LeapArray的窗口数组是循环使用的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值