UNPKG

tencentcloud-edgeone-migration-nodejs-v2

Version:

tencentcloud cdn config copy to edgeone

171 lines 6.41 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WarmUpTrafficShaping = void 0; const __1 = require("../.."); const plugins_1 = require("../../plugins"); const utils_1 = require("../../utils"); const kDefaultOptions = { /** * 预热时间 (s) */ warmTime: 10, /** * 预警因子 * 需大于等于 2 */ warnFactor: 3, /** * 空闲回收周期 */ idlePeriod: 3 }; /** * * 预热算法是一个另类的令牌桶算法,此算法实现(描述)了下述函数图像: * * ``` * ^ 1/QPS * | * 1/coldQPS + / * | / . * | / . * | / . * | / . * | / . * | / . * | / . * | / . * 1/stableQPS +----------/ . * | . Warm . ← `warmTime` = `warningTokens` 与 `maxTokens` 之间的面积 * | . Time . * | . . * 0 +----------+---------+---------------→ currentTokens * 0 warningTokens maxTokens * ``` * * 函数各变量满足如下关系: * * coldQPS = stableQPS/warnFactor * * (warningTokens/stableQPS)/(warmTime) = 1/(warnFactor-1) */ class WarmUpTrafficShaping { constructor(options) { this.type = plugins_1.PluginType.TrafficShaping; this.name = "warmup"; this.buckets = new WeakMap(); this.disposed = false; this.options = Object.assign(Object.assign({}, kDefaultOptions), options); } // #region dispose dispose() { this.disposed = true; } get isDisposed() { return this.disposed; } // #endregion inFlow(rule, partition) { if (this.disposed) { return Promise.reject(new __1.StateError("Already disposed")); } let bucket = this.buckets.get(rule); if (typeof bucket === "undefined") { bucket = this.buildBucket(rule, partition); this.buckets.set(rule, bucket); } if (bucket.partition !== partition) { /** few case */ const { maxTokens, remainingTokens, warningTokens, stableQPS, remainingQPS, coldQPS, slope } = this.buildBucket(rule, partition); bucket.maxTokens = maxTokens; bucket.warningTokens = warningTokens; bucket.stableQPS = stableQPS; bucket.coldQPS = coldQPS; bucket.slope = slope; bucket.remainingTokens = remainingTokens; bucket.remainingQPS = remainingQPS - bucket.usedQPS; if (bucket.remainingQPS < 0) { bucket.remainingQPS = 0; bucket.usedQPS = remainingQPS; } bucket.partition = partition; } if (bucket.timer === undefined) { bucket.timer = setInterval(() => { if (bucket === undefined) { (0, utils_1.UNREACHABLE)(); } if (this.disposed) { clearInterval(bucket.timer); bucket.timer = undefined; return; } this.timerTask(bucket); /** * 与初始状态相同时,可进入休眠 */ if (bucket.remainingTokens === bucket.maxTokens) { bucket.sleepPeriod += 1; } else { bucket.sleepPeriod = 0; } if (bucket.sleepPeriod > this.options.idlePeriod) { clearInterval(bucket.timer); bucket.timer = undefined; } }, 1 * utils_1.kSeconds).unref(); } if (bucket.remainingQPS >= 1) { bucket.remainingQPS -= 1; bucket.usedQPS += 1; return Promise.resolve(); } return Promise.reject(new __1.StateError("[plugin] [warmup], quota is limited")); } timerTask(bucket) { const { remainingTokens, warningTokens, stableQPS, coldQPS, usedQPS, slope } = bucket; if (remainingTokens < warningTokens /** 服务实例处于 "热" 状态 */ || (remainingTokens > warningTokens && usedQPS < coldQPS) /** 服务实例处于 "冷" 状态,并且当前一秒的 QPS 小于 `coldQPS` */) { // 增加 stableQPS 个 tokens,但总数不能超过 maxTokens bucket.remainingTokens += stableQPS; if (bucket.remainingTokens > bucket.maxTokens) { bucket.remainingTokens = bucket.maxTokens; } } bucket.remainingTokens -= usedQPS; bucket.usedQPS = 0; bucket.remainingQPS = this.calculateQPS(slope, warningTokens, bucket.remainingTokens, stableQPS); } calculateQPS(slope, warningTokens, remainingTokens, stableQPS) { if (remainingTokens <= warningTokens) { return stableQPS; } return stableQPS / (1 + (slope * stableQPS * (remainingTokens - warningTokens))); } buildBucket(rule, partition) { // 取 rules 中最小的 qps 作为 stableQPS const stableQPS = rule.amounts.reduce((acc, { amount, duration }) => { const qps = (amount / partition) / (duration / utils_1.kSeconds); if (qps < acc) { return qps; } return acc; }, Infinity); const coldQPS = stableQPS / this.options.warnFactor; const warningTokens = stableQPS * (this.options.warmTime / (this.options.warnFactor - 1)); const maxTokens = warningTokens + ((2 * this.options.warmTime) / ((1 / stableQPS) + (1 / coldQPS))); const slope = ((1 / coldQPS) - (1 / stableQPS)) / (maxTokens - warningTokens); return { remainingTokens: maxTokens, warningTokens, maxTokens, remainingQPS: this.calculateQPS(slope, warningTokens, maxTokens, stableQPS), usedQPS: 0, coldQPS, stableQPS, slope, partition, sleepPeriod: 0 }; } } exports.WarmUpTrafficShaping = WarmUpTrafficShaping; //# sourceMappingURL=warmup.js.map