【spring系列】Alibaba Sentinel(4)滑动窗口源码

系列文章目录

【spring系列】Alibaba Sentinel(1)快速启动
【spring系列】Alibaba Sentinel(2)流控详解
【spring系列】Alibaba Sentinel(3)降级详解



前言

之前简单的使用了下sentinel,通过sentinel实现了简单的流控降级的示例,sentinel真正的奥妙缺在这个滑动窗口,通过滑动窗口来记录每个时间段是否可以通过限制。


提示:以下是本篇文章正文内容,下面案例可供参考

一、滑动窗口实现原理

1. 实现思路

在这里插入图片描述
     Sentinel 底层采用高性能的滑动窗口数据结构 LeapArray 来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。

2.源码解析

LeapArray表示整个滑动窗口,其中有几个很重要的属性

代码如下(示例):

	//单口滑动时间窗口的大小,单位是毫秒
	//最小的单位,如上所图,这个值应该是200
    protected int windowLengthInMs;
	//滑动时间窗口个数 5
    protected int sampleCount;
	 //统计时间区间   1秒
    protected int intervalInMs;
	// 保存统计数据的地方,数组中5 个窗口
    protected final AtomicReferenceArray<WindowWrap<T>> array;

LeapArray中规定了窗口的基本属性,WindowWrap中保存了窗口的信息

 /**
     * 以毫秒为单位的时间窗口长度
     */
    private final long windowLengthInMs;

    /**
     * 窗口的开始时间,例如左边窗口的值为200
     */
    private long windowStart;

    /**
     * 数据统计,比如1秒中通过了几条请求,拦截了几条请求等等。
     */
    private T value;

LeapArray通过传入当前时间获取当前时间窗口,calculateTimeIdx获取窗口index;calculateWindowStart获取当前窗口的开始时间。

 public WindowWrap<T> currentWindow(long timeMillis) {
		//当前时间戳,肯定不能小于0拉
        if (timeMillis < 0) {
            return null;
        }
		//获取当前的时间窗口index
        int idx = calculateTimeIdx(timeMillis);
        // Calculate current bucket start time.
		//获取当前时间窗口的开始时间
        long windowStart = calculateWindowStart(timeMillis);

        /*
         * Get bucket item at given time from the array.
         *
         * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
         * (2) Bucket is up-to-date, then just return the bucket.
         * (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets.
         */
        while (true) {
            WindowWrap<T> old = array.get(idx);
			//如果当前窗口不存在,一般刚刚开始时候为null
            if (old == null) {
                /*
                 *     B0       B1      B2    NULL      B4
                 * ||_______|_______|_______|_______|_______||___
                 * 200     400     600     800     1000    1200  timestamp
                 *                             ^
                 *                          time=888
                 *            bucket is empty, so create new and update
                 *
                 * If the old bucket is absent, then we create a new bucket at {@code windowStart},
                 * then try to update circular array via a CAS operation. Only one thread can
                 * succeed to update, while other threads yield its time slice.
                 */
				 // 设置时间窗口的属性,窗口长度,窗口开始时间等等
                WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
				//通过CAS进行原子更新
                if (array.compareAndSet(idx, null, window)) {
                    // Successfully updated, return the created bucket.
                    return window;
                } else {
                    // Contention failed, the thread will yield its time slice to wait for bucket available.
                    Thread.yield();
                }
            } else if (windowStart == old.windowStart()) {
				//在同一个时间窗口,比如800为开始时间,第二个请求进来的时候,时间为888,
                /*
                 *     B0       B1      B2     B3      B4
                 * ||_______|_______|_______|_______|_______||___
                 * 200     400     600     800     1000    1200  timestamp
                 *                             ^
                 *                          time=888
                 */
                return old;
            } else if (windowStart > old.windowStart()) {
				// 不在同一个时间窗口的话,旧的窗口就没有作用拉,old窗口说明是旧的信息,则需要进行重置
                if (updateLock.tryLock()) {
                    try {
                        // Successfully get the update lock, now we reset the bucket.
                        return resetWindowTo(old, windowStart);
                    } finally {
                        updateLock.unlock();
                    }
                } else {
                    // Contention failed, the thread will yield its time slice to wait for bucket available.
                    Thread.yield();
                }
            } else if (windowStart < old.windowStart()) {
                // 一般这种情况不会存在,比如第一次设置的时间为12点整,第二次获取的时间为11点59分
				//理论上是不存在这种情况的
                return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            }
        }
    }

calculateTimeIdx

	//获取当前时间的时间窗口的index
    private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
		//   *     B0       B1      B2    NULL      B4
        //       * ||_______|_______|_______|_______|_______||___
        //       * 200     400     600     800     1000    1200  timestamp
	    //          |            window为1秒                |
		//	  *                                  ^
          //       *                         time=888
		  // 比如每个节点的间隔是200 ,当前时间为888,要获取当前时间在哪个节点中
        long timeId = timeMillis / windowLengthInMs;
        // Calculate current index so we can map the timestamp to the leap array.
		// 返回时间窗口的下标。如果有2个窗口,通过求余,来判断当前节点在哪个窗口中
        return (int)(timeId % array.length());
    }

MetricBucket 为LeapArray的泛型类 ,意思就是指标桶,,可以认为一个 MetricBucket 对象可以存储一个抽样时间段内所有的指标,例如一个抽象时间段中通过数量、阻塞数量、异常数量、成功数量、响应时间。通过LongAdder进行计数。一般多线程计数都使用CAS进行增加,比如AtomicIntegerLongAdder是Atomic的一种优化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叁滴水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值