public class WarmUpController implements TrafficShapingController {
protected double count;
private int coldFactor;
protected int warningToken = 0;
private int maxToken;
protected double slope;
protected AtomicLong storedTokens = new AtomicLong(0);
protected AtomicLong lastFilledTime = new AtomicLong(0);
public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
construct(count, warmUpPeriodInSec, coldFactor);
}
public WarmUpController(double count, int warmUpPeriodInSec) {
construct(count, warmUpPeriodInSec, 3);
}
/**
* count = 100 qps
* warmUpPeriodInSec 预热时间 30秒
* coldFactor 3
* warningToken = 30 * 100 / 2 1500
* maxToken = 1500 + 3000 4500
* slope 斜率用来计算当前token生成时间间隔 从来计算当前1s内能生成多少token 进而比较token的生成和消费速度
*
* = 2 / 100 /(3000) 0.0000006
* @param count
* @param warmUpPeriodInSec
* @param coldFactor
*/
private void construct(double count, int warmUpPeriodInSec, int coldFactor) {
if (coldFactor <= 1) {
throw new IllegalArgumentException("Cold factor should be larger than 1");
}
this.count = count;
this.coldFactor = coldFactor;
// thresholdPermits = 0.5 * warmupPeriod / stableInterval.
// warningToken = 100;10 * 100 / 2 500
warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
// / maxPermits = thresholdPermits + 2 * warmupPeriod /
// (stableInterval + coldInterval)
// maxToken = 200
// 1000
maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));
// slope
// 2 / 100 0.02
// slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits
// - thresholdPermits); 0.00006
// 0.00004 * 500 0.02 0.02 0.02
// 这个速率是什么原理计算的呢 跟冷却因子有关 首先我们可以根据 qps/coldFactor 得到一个最小值 即我们允许每秒钟最少通过多少个
// 比如冷却因子为3 qps为100 也就是说我们的速率是从33 开始的 也就是一开始直接进来33个请求我们是能承受的住的
// 然后随着 进来的请求过多 剩余超过警戒线令牌数的 令牌越来越少 我们的速率会提高 也就是速率是伴随这之前的请求数来的 之前的请求数越多 我们的速率就会越高 跟爬坡一样
slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
}
@Override
public boolean canPass(Node node, int acquireCount) {
return canPass(node, acquireCount, false);
}
/**
* 冷启动(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式。该方式主要用于系统长期处于低水位的情况下,
* 当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,
* 在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。具体的例子参见 WarmUpFlowDemo。
* @param node resource node
* @param acquireCount count to acquire
* @param prioritized whether the request is prioritized
* @return
*/
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
//当前1秒的qps
long passQps = (long) node.passQps();
// 获取当前桶的前一个桶 比如当前时间为1600 他会在第二个桶
//那么1600 -500 获得前一个桶
long previousQps = (long) node.previousPassQps();
syncToken(previousQps);
// 开始计算它的斜率
// 如果进入了警戒线,开始调整他的qps
long restToken = storedTokens.get();
//这一句就是判断 之前一段时间是否有突增流量 并且超过了警戒值
// 0.00004 * 500
if (restToken >= warningToken) {
long aboveToken = restToken - warningToken;
// 消耗的速度要比warning快,但是要比慢
// current interval = restToken*slope+1/count
/**
* 看这一句 非常重要 靠 折磨的我啊 你可能很有疑问 我靠 为什么我 每秒钟访问量那么少 你不放我通行 却还是要判断 你判断个毛哦
* 注意当前这个过滤器 是冷启动过滤器 是预防当一段时间内请求量很低 突然流量猛增 导致压垮系统
* 注意一点 我们刚进来 就会直接保存一个最大的令牌数 也就是会进来 认为现在还是在预热 就是系统还没跑起来 系统真正跑起来 起码得每秒能进来 设置的qps/2个
* 所以这里会拿 现存的令牌数 减去告警的 令牌数 得出一个每秒钟的令牌数
* 注意令牌数越多 证明当前越冷 那么就越严格 避免突然流量的增大 这也就是 冷启动的核心原理 当你跑热了的时候 允许你qps 无限接近设置的qps
* 但是不允许你从 之前每秒请求一次 突然增长到现在每秒请求qps/2次
*/
/**
* 这个速率是什么原理计算的呢 跟冷却因子有关 首先我们可以根据 qps/coldFactor 得到一个最小值 即我们允许每秒钟 通过多少个无所谓能接受
* 比如冷却因子为3 qps为100 也就是说我们的速率是从33 开始的 也就是一开始直接进来33个请求我们是能承受的住的
* 然后随着 进来的请求过多 剩余超过警戒线令牌数的 令牌越来越少 我们的速率会提高 也就是速率是伴随这之前的请求数来的 之前的请求数越多 我们的速率就会越高 跟爬坡一样
* slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
*/
//计算1秒内生成的令牌书 下面这个公示 如果我们不考虑 aboveToken * slope 其实结果就是count 因为 1 / (1 /100) 不就等于1 乘一个数的对数的对数 也就是乘自身
// 所以加上这一块 aboveToken * slope 就会导致 1 除的数 越来越约大 那如果aboveToken越小就代表生产的令牌数越多 是个小数 也就是会导致 结果越来越小
//所以如果令牌剩的越多 证明现在越冷 那么就代表当前每秒钟生成的令牌数被消耗的越小 就越是严格控制通过的数量
//所以这一块 slope 速率计算的公式很牛逼
double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
if (passQps + acquireCount <= warningQps) {
return true;
}
} else {
if (passQps + acquireCount <= count) {
return true;
}
}
return false;
}
protected void syncToken(long passQps) {
long currentTime = TimeUtil.currentTimeMillis();
currentTime = currentTime - currentTime % 1000;
long oldLastFillTime = lastFilledTime.get();
if (currentTime <= oldLastFillTime) {
return;
}
long oldValue = storedTokens.get();
long newValue = coolDownTokens(currentTime, passQps);
if (storedTokens.compareAndSet(oldValue, newValue)) {
long currentValue = storedTokens.addAndGet(0 - passQps);
if (currentValue < 0) {
storedTokens.set(0L);
}
lastFilledTime.set(currentTime);
}
}
private long coolDownTokens(long currentTime, long passQps) {
long oldValue = storedTokens.get();
long newValue = oldValue;
// 添加令牌的判断前提条件:
// 当令牌的消耗程度远远低于警戒线的时候
//warningToken 是根据warmUpPeriodInSec 来的 一般 等于
if (oldValue < warningToken) {//现在存在的令牌 是否低于告警值
// 如果低于 证明现在程序跑的很热 那么就再往令牌桶中放令牌 让程序拿到令牌 去执行 理想的情况下 应该是一直进来这个方法 一致生产这么多令牌 然后你差不多都能消耗 程序跑的很热
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
} else if (oldValue > warningToken) {
if (passQps < (int)count / coldFactor) {//这一步的目的是 当上一次请求量是符合预期的时候 当前令牌桶大于告警值的情况下 也生产令牌 认为本次是一次健康的运行
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
}
}
// 第一次进来时 会返回maxToken 也就是第一次进来就是高于警戒线的 currentTime - lastFilledTime.get()因为第一次进来lastFilledTime = 0 当前时间-0肯定非常大
return Math.min(newValue, maxToken);
}
}
WarmUpController原理
最新推荐文章于 2023-08-26 17:35:51 发布