sentinel-滑动窗口

本文解析了StatisticSlot中如何统计请求次数,特别是StatisticNode通过rollingCounterInSecond和rollingCounterInMinute实现的秒级和分钟级滑动窗口计数方法,以及LeapArray在动态窗口管理中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、统计StatisticSlot

StatisticSlot entry

@Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {
try {
    ...
    
    // 这里我们看到 对于thread量是有统计的 
    // 那么底层到底是怎么统计一段时间内的请求数量呢 继续往下看
    if (context.getCurEntry().getOriginNode() != null) {
        context.getCurEntry().getOriginNode().increaseThreadNum();
        // 从这个addPass进去
        context.getCurEntry().getOriginNode().addPassRequest(count);
    }

	...
}

二、StatisticNode

...

@Override
public void addPassRequest(int count) {
	// 这是一个秒内 毫秒级别的滑动窗口
	// private transient volatile 
	// Metric rollingCounterInSecond = new ArrayMetric(2, 1000);
	// | _ _ _ _ | _ _ _ _ |
	// 0         500      1000.  windowStart(可动态滑动, ms)
    rollingCounterInSecond.addPass(count);
    // 这是一个分钟内 秒级别的滑动窗口
    // private transient 
    // Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
    // | _ _ _ _ | _ _ _ _ | _ _ _ _| ... | _ _ _ _ | _ _ _ _ | _ _ _ _|
	// 0        1000      2000     3000   57000     58000      59000    60000.  windowStart(可动态滑动, ms)
    rollingCounterInMinute.addPass(count);
}

...

rollingCounterInSecond和rollingCounterInMinute就StatisticNode实例化的两个滑动窗口, 窗口很好理解, 那么滑动是如何滑动的呢, windowStart是如何计算的呢, 继续往下看窗口实例化的底层.

三、 LeapArray

ArrayMetric构造器

...

private final LeapArray<MetricBucket> data;

public ArrayMetric(int sampleCount, int intervalInMs) {
    this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
}

...

我们能从源码中看到, addPass时候大量用到了currentWindow拿到窗口再做add操作. 那么着重来看LeapArray的currentWindow方法, 我们用rollingCounterInMinute为例(sampleCount=60, intervalInMs=60*1000)

public LeapArray(int sampleCount, int intervalInMs) {
    AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);
    AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive");
    AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");

	// 窗口单位 1000ms
    this.windowLengthInMs = intervalInMs / sampleCount;
    // 60 * 1000
    this.intervalInMs = intervalInMs;
    // 60
    this.sampleCount = sampleCount;
	// new AtomicReferenceArray<>(60);
    this.array = new AtomicReferenceArray<>(sampleCount);
}
// timeMillis = 1600为例
public WindowWrap<T> currentWindow(long timeMillis) {
    if (timeMillis < 0) {
        return null;
    }
	// ((timeMillis / windowLengthInMs) % array.length())
	// (1600 / 1000) % 60 = 1
    int idx = calculateTimeIdx(timeMillis);
    // timeMillis - timeMillis % windowLengthInMs
    // 1600 - 1600 % 1000 = 1000
    // 这个windowStart就可以动态滑动窗口了
    long windowStart = calculateWindowStart(timeMillis);
    // 从上面这两个参数 就能确定出来该timeMillis 应该在minute窗口的下角标为1的窗口里, 并且windowStart为1000

    while (true) {
        WindowWrap<T> old = array.get(idx);
        // 如果是第一次进来 那么需要new 一个WindowWrap对象 存放windowStart参数
        if (old == null) {
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            if (array.compareAndSet(idx, null, window)) {
                return window;
            } else {
                Thread.yield();
            }
           // 如果timeMillis还在这个窗口内 那么还是使用这个WindowWrap来装数据
        } else if (windowStart == old.windowStart()) {
            return old;
           // 如果不在这个窗口内了 WindowWrap需要reset windowStart值
        } 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));
        }
    }
}

rollingCounterInMinute里就是60个窗口, 窗口里有pass相关数量计算, 那么就可以针对minute单位内的pass数量做统计, 就可以根据这个参数来服务治理.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值