UNPKG

tencentcloud-edgeone-migration-nodejs-v2

Version:

tencentcloud cdn config copy to edgeone

222 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PolarisRatelimitAdapter = void 0; const process_1 = require("process"); const __1 = require("../../../.."); const utils_1 = require("../../../../utils"); const rules_1 = require("../rules"); const types_1 = require("../types"); const utils_2 = require("../utils"); const base_1 = require("./base"); class PolarisRatelimitAdapter extends base_1.PolarisBaseAdapter { async ratelimitRules(namespace, service, revision) { if (!this.initializeStatus.initialized) { await this.waitForInitialized(); } const { logger } = this; const { transaction } = logger; if (logger.tracingEnabled) { logger.trace("Plugins" /* Plugins */, this.name, "ratelimitRules", transaction, `request ${namespace}.${service}${(revision !== null && revision !== void 0 ? revision : "") && `@${revision}`}`); } const response = await this.requestBackend(types_1.ServiceType.Discover, this.options.discoverService, "discover", { type: types_1.DiscoverRequestType.RATE_LIMIT, service: { namespace: this.box(namespace), name: this.box(service) } }); this.tracingResponse(transaction, "ratelimitRules", response); this.maybeErrorResponse(response, namespace, service); const { rateLimit } = response; if (!(rateLimit === null || rateLimit === void 0 ? void 0 : rateLimit.rules) || (revision !== undefined && this.unbox(rateLimit.revision, "") === revision)) { logger.trace("Plugins" /* Plugins */, this.name, "ratelimitRules", transaction, "empty rules or revision is equal"); return { data: [], revision: (rateLimit === null || rateLimit === void 0 ? void 0 : rateLimit.rules) ? (revision !== null && revision !== void 0 ? revision : "") : "" }; } let { ratelimitRuleProcessor } = this; if (ratelimitRuleProcessor === undefined) { ratelimitRuleProcessor = new rules_1.RuleProcessor(this.unbox.bind(this)); this.ratelimitRuleProcessor = ratelimitRuleProcessor; } const { rules } = rateLimit; const data = []; for (let i = 0; i < rules.length; i += 1) { // eslint-disable-line @typescript-eslint/prefer-for-of const rule = this.procRatelimitRule(ratelimitRuleProcessor, rules[i]); if (rule) { data.push(rule); } } logger.trace("Plugins" /* Plugins */, this.name, "ratelimitRules", transaction, "returned rules is", data); return { data, revision: this.unbox(rateLimit.revision, "") }; } // eslint-disable-next-line max-lines-per-function async acquireQuota(namespace, service, rule, used) { if (!this.initializeStatus.initialized) { await this.waitForInitialized(); } const { logger } = this; const { transaction } = logger; const key = `${namespace}#${service}`; logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "select backend with key:", key); const instanceResponse = await this.selectBackend(this.buildStickyConsumer(), this.options.ratelimitService, key); if (logger.tracingEnabled) { const { host, port } = instanceResponse.instance; logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, `selected backend is ${host}:${port}`); } let { ratelimitInfo } = this; if (ratelimitInfo === undefined) { ratelimitInfo = Object.create(null); this.ratelimitInfo = ratelimitInfo; } let info = ratelimitInfo[key]; /** * 如当前选出的远端实例与上次访问实例不相同(如前一实例配熔断等),则重置全部状态 */ if (!info || info.backendInstance !== instanceResponse.instance) { logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "current selected instance not equal to the last result, reset all status"); info = { backendInstance: instanceResponse.instance, initializedStatus: new WeakMap(), diffTime: 0 }; ratelimitInfo[key] = info; } const request = { key: this.box(`${rule.id}#${rule.revision}`), namespace: this.box(namespace), service: this.box(service), /** * |server| = |local| + |diff| */ timestamp: this.box(Math.floor(Date.now() + info.diffTime)) }; const initialized = info.initializedStatus.get(rule); let promise; let method; const prx = this.pool.getOrCreateClient(instanceResponse.instance, types_1.ServiceType.Ratelimit); const startTime = (0, process_1.uptime)(); if (initialized === true) { /** 如当前限流规则已初始化完成 */ request.useds = this.amountToLimiter(used); logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "call backend acquireQuota with args:", request); promise = prx.acquireQuota(request); method = "Acquire"; } else if (initialized === undefined) { /** 如当前限流规则在远端未初始化,则先进行初始化 */ request.totals = this.amountToLimiter(rule.amounts); logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "call backend initializeQuota with args:", request); promise = prx.initializeQuota(request); method = "Initialize"; info.initializedStatus.set(rule, promise); } else { /** 当前限流规则正在初始化 */ logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "waiting for initialized"); await initialized; logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "initialize completed, Re-call"); return this.acquireQuota(namespace, service, rule, used); } let response; try { response = await promise; } catch (e) { logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "call backend exception with", e); instanceResponse.update(false, ((0, process_1.uptime)() - startTime) * utils_1.kSeconds); if (method === "Initialize") { info.initializedStatus.delete(rule); } throw e; } const code = this.unbox(response.code, NaN); this.tracingResponse(transaction, "acquireQuota", response); instanceResponse.update(true, ((0, process_1.uptime)() - startTime) * utils_1.kSeconds, `${code || 0}`); try { this.maybeErrorResponse(response, namespace, service); } catch (e) { if (method === "Initialize") { info.initializedStatus.delete(rule); } throw e; } const serverTime = this.unbox(response.timestamp, NaN); if (!Number.isNaN(serverTime)) { /** * |diff| = |server| + |TTL| - |local| */ info.diffTime = serverTime + ((((0, process_1.uptime)() - startTime) / 2) * utils_1.kSeconds) - Date.now(); logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "recalculate diff time is", info.diffTime); } if (method === "Initialize") { info.initializedStatus.set(rule, true); logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "initialize completed, Re-call"); return this.acquireQuota(namespace, service, rule, used); } const result = []; response.sum_useds.forEach(({ amount, duration }) => { const globalAmount = this.unbox(amount, NaN); if (!Number.isNaN(globalAmount) && duration) { result.push({ amount: globalAmount, duration: (0, utils_2.protobuf2ms)(duration) }); } else { (0, utils_1.UNREACHABLE)(); } }); logger.trace("Plugins" /* Plugins */, this.name, "acquireQuota", transaction, "quota config is", result); return result; } procRatelimitRule(processor, rule) { const { id, amounts, action, cluster, labels, resource, priority, report: ruleReport, revision: ruleRevision, type, disable } = rule; const ruleId = this.unbox(id, ""); const rulePriority = this.unbox(priority, NaN); if (ruleId !== "" && !Number.isNaN(rulePriority) && amounts) { const result = { resource: resource !== null && resource !== void 0 ? resource : __1.LimitResource.QPS, type: type !== null && type !== void 0 ? type : __1.LimitType.Global, id: ruleId, priority: rulePriority, revision: this.unbox(ruleRevision, ""), action: this.unbox(action, ""), cluster: this.unbox(cluster, ""), enable: !this.unbox(disable, false), labels: processor.produceMetadata(labels), amounts: amounts.map(({ maxAmount, validDuration }) => ({ amount: this.unbox(maxAmount, NaN), duration: validDuration ? (0, utils_2.protobuf2ms)(validDuration) : NaN })).filter(({ amount, duration }) => amount >= 0 && duration >= 1 * utils_1.kSeconds /** 最小周期为 1s */) }; if (result.type === __1.LimitType.Global) { const percent = this.unbox(ruleReport === null || ruleReport === void 0 ? void 0 : ruleReport.amountPercent, NaN) / 100; const interval = (ruleReport === null || ruleReport === void 0 ? void 0 : ruleReport.interval) ? (0, utils_2.protobuf2ms)(ruleReport.interval) : NaN; if (percent > 0 && percent <= 100 && Number.isNaN(interval)) { result.report = { percent, interval }; } } if (result.amounts.length > 0) { return result; } } } amountToLimiter(amounts) { return amounts.map(({ amount, duration }) => ({ amount: this.box(amount), /** * 在 `protobuf` 的 JSON format 中,`google.protobuf.Duration` 类型为 string,所以这里需要强制转换 */ duration: (0, utils_2.ms2protobuf)(duration, this.format) })); } } exports.PolarisRatelimitAdapter = PolarisRatelimitAdapter; //# sourceMappingURL=ratelimit.js.map