UNPKG

tencentcloud-edgeone-migration-nodejs-v2

Version:

tencentcloud cdn config copy to edgeone

283 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Health = void 0; const location_1 = require("../location"); const metadata_1 = require("../metadata"); const plugins_1 = require("../plugins"); const utils_1 = require("../utils"); const kDefaultOptions = { /** * 检查间隔 * * 此值确定了,探活子系统的最小分辨率 */ detectInterval: 10 * utils_1.kSeconds, /** * 熔断超时时间 * * 未配置任何探活模块时生效 */ fusingTimeout: 5 * utils_1.kSeconds, /** * 状态变更上报间隔 */ reportInterval: 5 * utils_1.kMinutes, /** * 状态变更上报阈值 */ reportThreshold: 10000 }; class Health { constructor(logger, registry, lb, detectors, reporters, options) { this.logger = logger; this.registry = registry; this.lb = lb; this.detectors = detectors; this.reporters = reporters; this.pending = new WeakSet(); this.history = Object.create(null); this.countOfHistory = 0; this.disposed = false; this.options = Object.assign(Object.assign({}, kDefaultOptions), options); this.needReport = reporters.some(report => typeof report.statusChangelog === "function"); this.run(); } dispose() { if (this.reportTimer) { clearTimeout(this.reportTimer); this.reportTimer = undefined; } if (this.detectTimer) { clearTimeout(this.detectTimer); this.detectTimer = undefined; } this.disposed = true; } /* eslint-enable max-len */ changeStatus(namespace, service, ...args) { if (this.disposed) { return; } const statusLogger = this.needReport ? this.getServiceChangelog(namespace, service).status : undefined; if (args.length === 1) { args[0].forEach((change, instance) => { this.performStatusChange(namespace, service, instance, change.status, change.reason, statusLogger); }); } else { this.performStatusChange(namespace, service, args[0], args[1], args[2], statusLogger); } if (this.needReport) { this.scheduleReport(); } } recoverAll(namespace, service, instances) { if (this.disposed || !this.needReport) { return; } let intersection; instances.forEach((instance) => { if (instance.status !== 0 /* Normal */) { /** * 计算实例相交的部分 */ if (intersection === undefined) { intersection = { location: instance.location, metadata: instance.metadata }; } else { if (!(0, location_1.isEmptyLocation)(intersection.location)) { intersection.location = (0, location_1.intersectionLocation)(intersection.location, instance.location); } if (!(0, metadata_1.isEmptyMetadata)(intersection.metadata)) { intersection.metadata = (0, metadata_1.intersectionMetadata)(intersection.metadata, instance.metadata); } } } }); if (intersection !== undefined) { this.getServiceChangelog(namespace, service).recover.push({ intersection, time: Date.now() }); this.countOfHistory += 1; this.scheduleReport(); } } scheduleReport() { if (this.countOfHistory >= this.options.reportThreshold) { if (this.reportTimer !== undefined) { clearTimeout(this.reportTimer); this.reportTimer = undefined; } this.report(); } else if (this.reportTimer === undefined) { this.reportTimer = setTimeout(() => { this.reportTimer = undefined; this.report(); }, this.options.reportInterval).unref(); } } report() { const { history } = this; this.history = Object.create(null); this.countOfHistory = 0; Object.keys(history).forEach((namespace) => { const serviceHistory = history[namespace]; Object.keys(serviceHistory).forEach((service) => { this.reporters.forEach((reporter) => { var _a; (_a = reporter.statusChangelog) === null || _a === void 0 ? void 0 : _a.call(reporter, namespace, service, serviceHistory[service]).catch(err => this.disposed || this.logger.error(`[${reporter.name}] [statusChangelog]`, err)); }); }); }); } getServiceChangelog(namespace, service) { let ns = this.history[namespace]; if (!ns) { ns = Object.create(null); this.history[namespace] = ns; } let changelog = ns[service]; if (!changelog) { changelog = { recover: [], status: new Map() }; ns[service] = changelog; } return changelog; } performStatusChange(namespace, service, instance, status, reason, statusLogger) { var _a, _b; if (instance.status === status) { return; } const before = instance.status; if (statusLogger !== undefined) { let statusHistory = statusLogger.get(instance); if (statusHistory === undefined) { statusHistory = []; statusLogger.set(instance, statusHistory); } statusHistory.push({ time: Date.now(), after: status, before, reason }); this.countOfHistory += 1; } instance.status = status; (_b = (_a = this.lb).onStatusChange) === null || _b === void 0 ? void 0 : _b.call(_a, namespace, service, instance, before); } // #endregion // #region detector async detect() { const promises = []; const instanceStorage = this.registry.local(plugins_1.RegistryCategory.Instance); for (const namespace of Object.keys(instanceStorage)) { const namespaceInstance = instanceStorage[namespace]; for (const service of Object.keys(namespaceInstance)) { const instances = namespaceInstance[service].data; for (const instance of instances) { if (instance.status === 3 /* Fused */ && !this.pending.has(instance)) { instance.dynamicWeight = 0; promises.push(this.detectInstance(instance) .then(result => this.procDetectResult(namespace, service, instance, result), err => this.logger.error("[health] [detect]", err))); } } } } await Promise.all(promises); } procDetectResult(namespace, service, instance, result) { if (instance.status !== 3 /* Fused */) { return; } const totalDetectors = this.detectors.length; /** * 根据所有探活模块的探测结果切换当前实例状态: * * 成功:`Fused` ==(立即)==> `HalfOpen` * * 未知:`Fused` ==(fusingTimeout)==> `HalfOpen` * * 失败:保持当前实例状态,等待下次继续探测 */ switch (result) { case 0 /* Success */: { this.successfulDetection(namespace, service, instance, `[health] [detect], one of detectors(${totalDetectors}) successfully detected`); break; } case 2 /* Other */: { setTimeout(() => { this.successfulDetection(namespace, service, instance, `[health] [detect], ${totalDetectors > 0 ? `one of detectors(${totalDetectors}) detected result is unknown, and ` : ""}has exceeded the fusing timeout(${this.options.fusingTimeout}ms)`); }, this.options.fusingTimeout).unref(); break; } case 1 /* Failure */: { break; } default: { (0, utils_1.UNREACHABLE)(); } } } successfulDetection(namespace, service, instance, reason) { /** * 由于为异步探活,实例状态可能已被其它模块修改, * 如果当前状态不为 `Fused` 则不进行切换。 */ if (instance.status === 3 /* Fused */) { this.changeStatus(namespace, service, instance, 1 /* HalfOpen */, reason); } } async detectInstance(instance) { const totalDetectors = this.detectors.length; for (let i = 0; i < totalDetectors; i += 1) { const result = await this.detectors[i].detect(instance); switch (result) { case 1 /* Failure */: case 2 /* Other */: { return result; } case 0 /* Success */: { break; } default: { (0, utils_1.UNREACHABLE)(); } } } if (totalDetectors > 0) { return 0 /* Success */; } return 2 /* Other */; } run() { if (this.detectTimer) { return; } /* * 为了性能考虑 `Health` 流程采用同一定时器驱动 * tradeoff: * 在最坏的情况下(未配置任何探活模块时), * 由 `Fused` ---> `Normal` 至少需要等待 `detectInterval + fusingTimeout` * 而非 `fusingTimeout` 中指定的值。 */ this.detectTimer = setTimeout(() => { this.detect().then(() => { if (this.disposed) { return; } this.detectTimer = undefined; this.run(); }, () => { (0, utils_1.UNREACHABLE)(); }); }, this.options.detectInterval).unref(); } } exports.Health = Health; //# sourceMappingURL=health.js.map