WarmUpController原理

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);
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值