UNPKG

tencentcloud-edgeone-migration-nodejs-v2

Version:

tencentcloud cdn config copy to edgeone

316 lines 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExternalSelector = exports.InternalSelector = void 0; const errors_1 = require("../errors"); const instance_1 = require("../instance"); const location_1 = require("../location"); const metadata_1 = require("../metadata"); const plugins_1 = require("../plugins"); const kDefaultOptions = { /** * 探活任务最大探测次数 */ maxProbes: 3, /** * 是否启用全死全活,当节点全部不健康时,是否返回 */ enableRecover: true }; /** * @description * 实例选取规则: * in ---> [Router Chain] ---> out */ class InternalSelector { constructor(global, logger, health, registry, routers, lb, options) { this.global = global; this.logger = logger; this.health = health; this.registry = registry; this.routers = routers; this.lb = lb; this.fuseStat = Object.create(null); this.requisite = plugins_1.RequisiteBitfield.None; this.disposed = false; this.options = Object.assign(Object.assign({}, kDefaultOptions), options); /** * 合并全部路由插件所需的前置要求 */ this.requisite = this.routers.reduce((acc, cur) => acc | cur.requisite, plugins_1.RequisiteBitfield.None); } dispose() { this.disposed = true; } async select(callee, caller, args = {}) { if (this.disposed) { throw new errors_1.StateError("Already disposed"); } const { logger } = this; const { transaction } = logger; let rules; // #region RequisiteBitfield.Rule if (this.requisite & plugins_1.RequisiteBitfield.Rule) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "start fetch callee routing rule"); const promises = [this.registry.fetch(plugins_1.RegistryCategory.Rule, callee.namespace, callee.service)]; if (caller) { const { namespace, service } = caller; /** * 主调 `namespace` 与 `service` 均填写才查询规则, * 否则仅使用 `metadata` 与规则的 `sources.metadata` 进行匹配 */ if (namespace && service) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "start fetch caller routing rule"); promises.push(this.registry.fetch(plugins_1.RegistryCategory.Rule, caller.namespace, caller.service)); } } /** * 规则路由仅对一方生效 * 入站规则 `in` 优先于出站规则 `out` */ ({ in: rules } = await promises[0]); if (promises.length > 1) { if (rules.length === 0) { ({ out: rules } = await promises[1]); } else { promises[1].catch(() => { }); } } logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "combined callee and caller routing rule:", rules); } else { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "plugins do not require routing rule"); } // #endregion const { filtered, recovered, service, transfer } = await this.routing(callee, rules, caller, args[plugins_1.PluginType.ServiceRouter]); /** * 如果存在 `transfer` 转发, * 则将 `transfer` 作为目标服务名,再次调用查询。 * _当前查询作废(不再进行任何匹配),以新查询结果作为返回_ */ if (transfer) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "transfer to service:", transfer); callee.service = transfer; return this.select(callee, caller, args); } logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "routing plugins filtered instances is", filtered); if (filtered.length > 0) { /** hot path */ if (recovered) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "recovered all instances"); /* * note: * 为了更快返回,这里采用异步上报 */ setImmediate(() => { this.health.recoverAll(callee.namespace, service, filtered); }); } const instances = this.choose(callee.namespace, service, filtered, args[plugins_1.PluginType.LoadBalancer]); logger.trace("Consumer" /* Consumer */, InternalSelector.name, "select", transaction, "lb plugin choosen and method returned instance is", filtered); return instances; } return null; } async list(namespace, service) { if (this.disposed) { throw new errors_1.StateError("Already disposed"); } return this.registry.fetch(plugins_1.RegistryCategory.Instance, namespace, service); } async rules(namespace, service) { if (this.disposed) { throw new errors_1.StateError("Already disposed"); } return this.registry.fetch(plugins_1.RegistryCategory.Rule, namespace, service); } async executor(callee, destination, source, instances = []) { const { service, metadata, level } = destination; let requestedService = ""; let pickedInstances = instances; // #region Step 1 - 当备选实例为空时,通过服务名获取服务所有实例 if (instances.length === 0) { /** * `service` 不存在、为空或为 `"*""` 时,则认为可访问用户请求的目标服务 */ requestedService = !service || service === "*" ? callee.service : service; pickedInstances = await this.registry.fetch(plugins_1.RegistryCategory.Instance, callee.namespace, requestedService); } if (pickedInstances.length === 0) { return { instances: [], service: requestedService }; } // #endregion // #region Step 2 - 匹配 `Metadata` 规则 if (metadata) { pickedInstances = pickedInstances.filter(instance => { var _a; return (0, metadata_1.isMetadataMatch)(instance.metadata, metadata, this.global.globalVariables, (_a = source === null || source === void 0 ? void 0 : source.parameters) !== null && _a !== void 0 ? _a : {}); }); } if (pickedInstances.length === 0) { return { instances: [], service: requestedService }; } // #endregion // #region Step 3 - 匹配 `Location` 位置信息 const { location } = this.registry; if (location && level !== undefined) { const nearbyInstances = pickedInstances.filter(instance => (0, location_1.isLocationMatch)(instance.location, location, level)); if (nearbyInstances.length > 0 || level !== 0 /* Region */) { pickedInstances = nearbyInstances; } } // #endregion return { instances: pickedInstances, service: requestedService }; } /* eslint-enable @typescript-eslint/unified-signatures */ // eslint-disable-next-line max-lines-per-function async routing(callee, rules, caller, args, // #region for recursive call only routers = this.routers, service = callee.service, filtered = [] // #endregion ) { var _a; const { logger } = this; const { transaction } = logger; // #region 栈顶 if (routers.length === 0) { let choosable = filtered.filter(instance => (0, instance_1.isChoosableInstance)(instance)); let recovered = false; if (choosable.length === 0 && this.options.enableRecover) { choosable = filtered; recovered = true; } logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "reached the top of the stack, and returned instances is", choosable); return { filtered: choosable, service, recovered }; } // #endregion const [router] = routers; let routingChain = routers.slice(1); let currentInstances = []; let currentService = ""; let hasFound = false; let bypassThis = false; for (const queryCommand of router.query(callee, rules, caller, args === null || args === void 0 ? void 0 : args[router.name])) { const { source, destination, controller } = queryCommand; if (logger.tracingEnabled) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, `destination query command generated by plugin ${router.name}:`, queryCommand); } if (destination === null || destination === void 0 ? void 0 : destination.transfer) { return { filtered: [], transfer: destination.transfer, service }; } const result = await this.executor(callee, destination !== null && destination !== void 0 ? destination : { service: "*" }, source, filtered); ({ instances: currentInstances, service: currentService } = result); logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "execute command and get instances is", currentInstances, "service is", currentService); hasFound || (hasFound = currentInstances.length > 0); let action; if (typeof router.filter === "function") { ({ filtered: currentInstances, action } = router.filter(currentInstances, queryCommand, args === null || args === void 0 ? void 0 : args[router.name])); if (logger.tracingEnabled) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, `plugin ${router.name} implemented filter method and get filtered instances is`, currentInstances); } } // #region Controller if (controller) { action || (action = controller[hasFound ? plugins_1.RoutingCondition.Found : plugins_1.RoutingCondition.NotFound]); } let needBreak = false; if (action) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "plugin output and override action is", action); // #region RoutingAction.Break needBreak = (_a = action[plugins_1.RoutingAction.Break]) !== null && _a !== void 0 ? _a : false; // #endregion // #region RoutingAction.Bypass const bypassedRouters = action[plugins_1.RoutingAction.Bypass]; if (bypassedRouters) { routingChain = routingChain.filter(node => bypassedRouters.indexOf(node.name) === -1); if (bypassedRouters.indexOf(router.name) !== -1) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "bypass current routing layer"); currentInstances = filtered; bypassThis = true; } } // #endregion } // #endregion if (needBreak || bypassThis || currentInstances.length > 0) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "stop query current plugin"); break; } } currentService || (currentService = service); if (currentInstances.length > 0 || (bypassThis && filtered.length === 0)) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "call next routing layer"); return this.routing(callee, rules, caller, args, routingChain, currentService, currentInstances); } logger.trace("Consumer" /* Consumer */, InternalSelector.name, "routing", transaction, "returned empty instances"); return { filtered: [], service: currentService }; } choose(namespace, service, instances, args) { const { logger } = this; const instance = this.lb.choose(namespace, service, instances, args); logger.trace("Consumer" /* Consumer */, InternalSelector.name, "choose", "plugin choosen and returned instance is", instance); /* * 如负载均衡插件选出的实例状态为 `HalfOpen`,则根据实例调用次数进行不同的处理: * * 实例调用次数小于 `maxProbes` 时:返回此实例,并将 _调用次数 + 1_ * * 实例调用次数等于 `maxProbes` 时: * * 设置实例状态为半关闭 `HalfClose` * * 将调用次数重置为 0 */ if (instance.status === 1 /* HalfOpen */) { let fuseStat = this.fuseStat[`${namespace}.${service}`]; if (!fuseStat) { fuseStat = new WeakMap(); this.fuseStat[`${namespace}.${service}`] = fuseStat; } const totalCall = (fuseStat.get(instance) || 0) + 1; if (totalCall < this.options.maxProbes) { fuseStat.set(instance, totalCall); } else if (totalCall === this.options.maxProbes) { if (logger.tracingEnabled) { logger.trace("Consumer" /* Consumer */, InternalSelector.name, "choose", "change instance status to HalfClose, because totalCall(${totalCall}) has reached maxProbes(${this.options.maxProbes})"); } this.health.changeStatus(namespace, service, instance, 2 /* HalfClose */, `[selector] [choose], totalCall(${totalCall}) == maxProbes(${this.options.maxProbes})`); fuseStat.set(instance, 0); } } return instance; } } exports.InternalSelector = InternalSelector; class ExternalSelector { constructor(naming) { this.naming = naming; /** * (empty function) */ } async select(callee) { return this.naming.select(callee.namespace, callee.service, callee.metadata); } async list(namespace, service) { const { data: instances } = await this.naming.list(namespace, service); return instances; } async rules(namespace, service) { return (await this.naming.routingRules(namespace, service)).data; } } exports.ExternalSelector = ExternalSelector; //# sourceMappingURL=selector.js.map