简介
其实sentinel核心原理并不难理解,就是在访问被保护资源时,根据实时的统计信息和预先定义的规则,检查是否可以访问。对我自己而言,可能统计的算法是比较关心的,都知道sentinel的统计算法是滑动时间窗口算法,这个算法一这么说就不接地气了,下面谈谈我们农村的说法。
探索
说这个算法主要要解决什么事呢?就是说在某个时间间隔要做点统计,比如说现在时间是16:06:21,320,我现在想知道16:06:21,000-16:06:21,320我的应用app通过了多少个请求,即当前的320ms通过了多少请求?哎这个简单啊,等我数数。。。,直接说说sentinel怎么搞的,想想文字说起来苍白无力,来点图片看看。

就如图片展示的,想统计一段时间内的数据,就定义一个WindowWrap(时间窗口)来实现,在时间轴上随着时间的流逝,会有无限多连续的时间窗口,时间窗口通过MetricBucket来统计数据,都统计什么数据呢,MetricEvent定义了相关的统计数据类型,不同的统计数据类型由一个LongAdder来计数,这样在请求到来的时候先根据当前时间戳定位时间窗口,由当前的时间窗口来记录需要统计的信息,至于LeapArray是怎么回事,代码逻辑比较容易理解,但是设计意图在这里还不是特别清晰,可以看看其sampleCount这个属性,应该能有所猜测,再多的可以到下文再仔细品味下。
咋就好像说完了呢,还不到10块钱的,再继续聊聊,上代码,不详细说为什么代码执行流程了,就看看关键的代码。
public class{
private final LeapArray<MetricBucket> data;
public void addPass(int count) {
WindowWrap<MetricBucket> wrap = data.currentWindow();
wrap.value().addPass(count);
}
}
public abstract class LeapArray{
public WindowWrap<T> currentWindow(long timeMillis) {
if (timeMillis < 0) {
return null;
}
//计算当前时间在数组中的位置
int idx = calculateTimeIdx(timeMillis);
//计算当前时间窗口的开始时间
long windowStart = calculateWindowStart(timeMillis);
while (true) {
//在数组中获取时间窗口,注意这里的old是说时间窗口已经创建过了
WindowWrap<T> old = array.get(idx);
//如果没有就搞一个新的时间窗口
if (old == null) {
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
if (array.compareAndSet(idx, null, window)) {
return window;
} else {
Thread.yield();
}
//如果开始时间等于old的开始时间就把old返回
} else if (windowStart == old.windowStart()) {
return old;
//如果大于old开始时间,就重置
} 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));
}
}
}
}
public class MetricBucket {
public MetricBucket add(MetricEvent event, long n) {
//找到对应的事件LongAdder递增
counters[event.ordinal()].add(n);
return this;
}
}
结合代码又进一步了解了sentinel的滑动时间窗口算法,一段时间(事件窗口)+统计对象,即时间在时间窗口中滑动并作数据统计。说到这里其实算法这块差不多结束了,不行我这暴脾气上来了,就说咋统计了,那统计完咋用的啊?那就再看看。
public class StatisticNode implements Node {
public long totalPass() {
return rollingCounterInMinute.pass();
}
}
public class ArrayMetric implements Metric {
public long pass() {
data.currentWindow();
long pass = 0;
List<MetricBucket> list = data.values();
for (MetricBucket window : list) {
pass += window.pass();
}
return pass;
}
}
public class MetricBucket {
public long pass() {
return get(MetricEvent.PASS);
}
public long get(MetricEvent event) {
return counters[event.ordinal()].sum();
}
}
这就明白了,不就是找在固定时间间隔内所有时间窗口样本的统计数据嘛,还记得sampleCount(LeapArray)吗?好像有点内味了,再看看qps。
public class StatisticNode implements Node {
public double passQps() {
return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
}
}
qps=所有样本请求通过数/固定时间间隔,呦,这么一看我可以把时间间隔搞得小一点比如(500ms,默认1000ms),那样本数我也不搞那么多,我让窗口长度就是500,那统计qps的时候就是1个500ms的qps,这样好像避免了统计学的误差哈,那要是。。。,剩下的自己动手吧。
总结
有兴趣可以自己实现下滑动时间窗口算法,有了sentinel的启发,相信实现起来难度不会太大,那再多说一下啊如果去掉时间二字,就变成了滑动窗口算法,这个算法的定义百度一下都能找得到,那我们可以再思索下sentinel的滑动时间窗口算法在算法角度,到底带来了多大的好处。