Sentinel学习笔记(1)-- 流量统计代码解析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/dainandainan1/article/details/86741738

 

代码解析博客地址:https://www.jianshu.com/p/7936d7a57924

结论图:

总结:slot Chain 是整体的一个骨架结构,之间的传导数据是entry.所有slot Chain 获取的基本数据都在Entry中。Entry 代表着一个信号,代表一个请求的到来,若果没有报任何错误,就按照正常的时间框算法进行统计。

可以看出Entry在统计这个数据中扮演的角色就是一个触发点。

阿里这个统计算法有几个特色:

1.基本上采用CAS的策略来对时间框对象WindowWrap操作,避免使用一些效率低的关键字,这是在处理高并发情况下,对同一个对象操作的高效手法

注意点:对数据操作包括创建时候一定保证创建的唯一性updateLock.tryLock()

while (true) {
    WindowWrap<T> old = array.get(idx);
    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());
        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()) {
        /*
         *     B0       B1      B2     B3      B4
         * ||_______|_______|_______|_______|_______||___
         * 200     400     600     800     1000    1200  timestamp
         *                             ^
         *                          time=888
         *            startTime of Bucket 3: 800, so it's up-to-date
         *
         * If current {@code windowStart} is equal to the start timestamp of old bucket,
         * that means the time is within the bucket, so directly return the bucket.
         */
        return old;
    } else if (windowStart > old.windowStart()) {
        /*
         *   (old)
         *             B0       B1      B2    NULL      B4
         * |_______||_______|_______|_______|_______|_______||___
         * ...    1200     1400    1600    1800    2000    2200  timestamp
         *                              ^
         *                           time=1676
         *          startTime of Bucket 2: 400, deprecated, should be reset
         *
         * If the start timestamp of old bucket is behind provided time, that means
         * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.
         * Note that the reset and clean-up operations are hard to be atomic,
         * so we need a update lock to guarantee the correctness of bucket update.
         *
         * The update lock is conditional (tiny scope) and will take effect only when
         * bucket is deprecated, so in most cases it won't lead to performance loss.
         */
        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()) {
        // Should not go through here, as the provided time is already behind.
        return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket());
    }
}

2.设计时候考虑了两种粒度,一种是秒级别,一种是分级别的

/**
 * Holds statistics of the recent {@code INTERVAL} seconds. The {@code INTERVAL} is divided into time spans
 * by given {@code sampleCount}.
 */
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / 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(1000, 60);

3.在获取时间的时候创建了一个TimeUtil,如果对性能要求比较高,这个时间的获取也是设计的点

/**
 * Provides millisecond-level time of OS.
 *
 * @author qinan.qn
 */
public final class TimeUtil {

    private static volatile long currentTimeMillis;

    static {
        currentTimeMillis = System.currentTimeMillis();
        Thread daemon = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    currentTimeMillis = System.currentTimeMillis();
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);
                    } catch (Throwable e) {

                    }
                }
            }
        });
        daemon.setDaemon(true);
        daemon.setName("sentinel-time-tick-thread");
        daemon.start();
    }

    public static long currentTimeMillis() {
        return currentTimeMillis;
    }
}

关于流量这一块可以参考这:https://www.jianshu.com/p/938709e94e43

其实流量这一块主要还是要明白令牌漏斗算法。这个是一个经典简单的算法,在Sentinel中,开发者设计三种基本模式,分别是:

Rate Limiter(PaceController) 匀速器,Warm Up 冷启动方式,Default 默认方式

这三种方法的原理可以在博客中看到,比较难理解的就是Warm Up 冷启动方式,可以参考https://www.jianshu.com/p/280bf2dbd6f0,两者原理一样,保证以固定的斜率上涨。

 

 

展开阅读全文

没有更多推荐了,返回首页