一、统计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数量做统计, 就可以根据这个参数来服务治理.