Sentinel 官网地址 :官网地址
算法实现类
获取&初始化窗口 leapArray.currentWindow()
public WindowWrap<T> currentWindow(long timeMillis) {
if (timeMillis < 0) {
return null;
}
//计算出位于那个窗口
int idx = calculateTimeIdx(timeMillis);
// Calculate current bucket start time.
long windowStart = calculateWindowStart(timeMillis);
while (true) {
//根据下标获取对应窗口
WindowWrap<T> old = array.get(idx);
//初始化窗口
if (old == null) {
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()) {
//在当前窗口内 直接返回
return old;
} else if (windowStart > old.windowStart()) {
//下一个周期 更新当前窗口时间,重置数据 实现窗口复用
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(timeMillis));
}
}
}
链路调用判断是否限流
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
//获取过去时间内的有效请求数
int curCount = avgUsedTokens(node);
//过去时间内的有效请求数 +1 是否大于限流数
if (curCount + acquireCount > count) {
if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
long currentTime;
long waitInMs;
currentTime = TimeUtil.currentTimeMillis();
waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
node.addWaitingRequest(currentTime + waitInMs, acquireCount);
node.addOccupiedPass(acquireCount);
sleep(waitInMs);
// PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
throw new PriorityWaitException(waitInMs);
}
}
return false;
}
return true;
}
private int avgUsedTokens(Node node) {
if (node == null) {
return DEFAULT_AVG_USED_TOKENS;
}
return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
}
public double passQps() {
return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
}
public long pass() {
data.currentWindow();
long pass = 0;
//data.values 返回有效数据 当前时间减去窗口开始时间大于统计时间的丢弃
//三个窗口开始时间 100 500 1000 当前 1600 则 窗口100,500丢弃不纳入请求数统计 1600-100>1000,1600-500>1000
List<MetricBucket> list = data.values();
for (MetricBucket window : list) {
pass += window.pass();
}
return pass;
}
测试代码
public static void main(String[] sre) throws InterruptedException {
long count = 2, totalPass = 0;
//初始化一个滑动窗口 5s 分为5个窗口 每个窗口1s
BucketLeapArray leapArray = new BucketLeapArray(5, 1000*5);
ArrayMetric arrayMetric = new ArrayMetric(leapArray);
leapArray.currentWindow().value().addPass(1);
//线程沉睡唤醒需要消耗时间 测试时选择较大时间来减少误差
Thread.sleep(1000);
leapArray.currentWindow().value().addPass(1);
Thread.sleep(1000);
leapArray.currentWindow().value().addPass(1);
Thread.sleep(1000);
leapArray.currentWindow().value().addPass(1);
totalPass = arrayMetric.pass();
leapArray.debug(System.currentTimeMillis());
if (totalPass+1>count){
//something... 限流了
}
System.out.println("通过请求数"+totalPass);
Thread.sleep(2100);
//窗口 1000 2000 3000 4000 5000 当前6100 pass()时触发 leapArray.currentWindow() 满足条件windowStart > old.windowStart() 重置时间,重置数据
totalPass = arrayMetric.pass();
//对比两次debug输出 可以发现第二次有一个窗口时间被更新了,实现了窗口的复用统计
leapArray.debug(System.currentTimeMillis());
System.out.println("通过请求数"+totalPass);
}