tencentcloud-edgeone-migration-nodejs-v2
Version:
tencentcloud cdn config copy to edgeone
660 lines • 27.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalRegistry = void 0;
const events_1 = require("events");
const process_1 = require("process");
const errors_1 = require("./errors");
const instance_1 = require("./instance");
const location_1 = require("./location");
const plugins_1 = require("./plugins");
const utils_1 = require("./utils");
var EntryStatus;
(function (EntryStatus) {
/** 正常 */
EntryStatus[EntryStatus["Normal"] = 0] = "Normal";
/** 过期 */
EntryStatus[EntryStatus["Outdated"] = 1] = "Outdated";
/** 更新中 */
EntryStatus[EntryStatus["Updating"] = 2] = "Updating";
/** 已释放 */
EntryStatus[EntryStatus["Released"] = 3] = "Released";
})(EntryStatus || (EntryStatus = {}));
const kDefaultOptions = {
// #region Time
/**
* 过期时间
* 超过此时间,会 __异步__ 请求新数据
* _也就是说,返回值可能为旧数据_
*/
expireTime: {
/** 实例过期时间 */
[plugins_1.RegistryCategory.Instance]: 2 * utils_1.kSeconds,
/** 规则过期时间 */
[plugins_1.RegistryCategory.Rule]: 2 * utils_1.kSeconds,
/** 限流规则过期时间 */
[plugins_1.RegistryCategory.Ratelimit]: 2 * utils_1.kSeconds
},
/**
* 刷新时间
* 达到此时间,会自动更新数据(如数据已过期)
* _数据刷新,并不影响资源回收策略_
*/
refreshTime: {
/** 实例刷新时间 */
[plugins_1.RegistryCategory.Instance]: 10 * utils_1.kSeconds,
/** 规则刷新时间 */
[plugins_1.RegistryCategory.Rule]: 10 * utils_1.kSeconds,
/** 限流规则刷新时间 */
[plugins_1.RegistryCategory.Ratelimit]: 10 * utils_1.kSeconds
},
/**
* 脏数据时间
* 超过此时间,会 __同步__ 请求新数据
* _也就是说,返回值一定为新数据_
*/
dirtyTime: {
/** 实例脏数据时间 */
[plugins_1.RegistryCategory.Instance]: 1 * utils_1.kMinutes,
/** 规则脏数据时间 */
[plugins_1.RegistryCategory.Rule]: 1 * utils_1.kMinutes,
/** 限流规则脏数据时间 */
[plugins_1.RegistryCategory.Ratelimit]: 1 * utils_1.kMinutes
},
/**
* 资源回收时间
* 回收条件为:_至少_经历指定的间隔都没被访问
* 小于或等于 0 则为不启用此特性
*/
recycleTime: 1 * utils_1.kDays,
// #endregion
// #region Report
/**
* 服务状态上报间隔
*/
reportInterval: 3 * utils_1.kMinutes,
/**
* 服务状态上报阈值
*/
reportThreshold: 10000,
// #endregion
/**
* 请求池最大容量
* 超过此大小,且无请求时,将会进行回收
*/
maxRequestsCapacity: 18 /** v8 对于 FastProperties Slot 的上限为 18 */
};
// #endregion
class LocalRegistry extends events_1.EventEmitter {
constructor(logger, naming, registry, reporters, ratelimit, options) {
super();
this.logger = logger;
this.naming = naming;
this.registry = registry;
this.reporters = reporters;
this.ratelimit = ratelimit;
this.entriesMeta = this.createEntries();
this.servicesMeta = Object.create(null);
// #region request
this.activeRequestsPool = Object.create(null);
this.activeRequestsCapacity = 0;
this.activeRequestsCount = 0;
// #endregion
// #region report
this.historyRecorder = Object.create(null);
this.historyRecorderCount = 0;
this.needReport = false;
this.disposed = false;
this.options = Object.assign(Object.assign(Object.assign({}, kDefaultOptions), options), { expireTime: Object.assign(Object.assign({}, kDefaultOptions.expireTime), options === null || options === void 0 ? void 0 : options.expireTime), refreshTime: Object.assign(Object.assign({}, kDefaultOptions.refreshTime), options === null || options === void 0 ? void 0 : options.refreshTime), dirtyTime: Object.assign(Object.assign({}, kDefaultOptions.dirtyTime), options === null || options === void 0 ? void 0 : options.dirtyTime) });
this.needReport = reporters.some(report => typeof report.registryCache === "function");
}
get location() {
const { location } = this.naming;
const { baseLocation } = this.options;
if (baseLocation && (0, location_1.isEmptyLocation)(location)) {
return baseLocation;
}
return location;
}
async fetch(type, namespace, service) {
if (this.disposed) {
throw new errors_1.StateError("Already disposed");
}
if (namespace === "" && service === "") {
switch (type) {
case plugins_1.RegistryCategory.Instance: {
return [];
}
case plugins_1.RegistryCategory.Rule: {
return {
in: [],
out: []
};
}
case plugins_1.RegistryCategory.Ratelimit: {
return [];
}
default: {
(0, utils_1.UNREACHABLE)();
}
}
}
/** 标记对 `namespace.service` 的访问 */
this.accessService(namespace, service);
const fromLocal = this.local(type, namespace, service);
// #region remote
if (fromLocal === null) {
const fromNaming = await this.pull(type, namespace, service);
if (fromNaming !== null) {
return fromNaming;
}
/*
* note:
* 如果前一相同查询设置了数据,
* 则重走标准逻辑获取数据。
*/
return this.fetch(type, namespace, service);
}
// #endregion
const entryMeta = this.getEntryMeta(type, namespace, service);
switch (entryMeta.status) {
case EntryStatus.Outdated: {
/**
* `timeSpan` >= dirtyTime
* 删除本地所有缓存数据,重新获取
*/
if (entryMeta.outdatedTime === undefined) {
(0, utils_1.UNREACHABLE)();
}
if (((0, process_1.uptime)() - entryMeta.outdatedTime) * utils_1.kSeconds >= this.options.dirtyTime[type]) {
this.dropService([type], namespace, service);
return this.fetch(type, namespace, service);
}
/*
* `timeSpan` < dirtyTime
* 异步更新缓存数据
*
* note:
* 更新机制为异步更新,
* 避免缓存击穿,及在特定场景下死循环
*/
this.performUpdate(type, namespace, service, fromLocal, entryMeta).catch(e => this.logger.error(`[Registry] [fetch], update ${type}`, e));
break;
}
case EntryStatus.Normal: {
if (entryMeta.statusUpdater === null) {
(0, utils_1.UNREACHABLE)();
}
break;
}
case EntryStatus.Updating: {
break;
}
case EntryStatus.Released: // [[fallthrough]]
default: {
(0, utils_1.UNREACHABLE)();
}
}
return fromLocal.data;
}
/* eslint-enable max-len */
// #endregion
local(type, namespace, service) {
if (!namespace || !service) {
return this.registry[type].all();
}
return this.registry[type].get(namespace, service);
}
// #endregion
// #region dispose
dispose() {
this.cancelHistoryReporter();
this.dropEntryMeta();
this.dropServiceMeta();
this.removeAllListeners();
this.disposed = true;
}
get isDisposed() {
return this.disposed;
}
// #endregion
// #region update
/** for external use only */
async update(type, namespace, service) {
/** 标记对 `namespace.service` 的访问 */
this.accessService(namespace, service);
return this.performUpdate(type, namespace, service);
}
async performUpdate(type, namespace, service, fromLocal = this.local(type, namespace, service), entryMeta) {
if (fromLocal === null) {
return (await this.pull(type, namespace, service)) !== null;
}
const localEntryMeta = entryMeta !== null && entryMeta !== void 0 ? entryMeta : this.getEntryMeta(type, namespace, service);
switch (localEntryMeta.status) {
case EntryStatus.Normal: // [[fallthrough]]
case EntryStatus.Outdated: {
break;
}
case EntryStatus.Updating: {
return false;
}
case EntryStatus.Released: // [[fallthrough]]
default: {
(0, utils_1.UNREACHABLE)();
}
}
localEntryMeta.status = EntryStatus.Updating;
return Promise.resolve() /** 通过 Promise 切分同步执行代码,降低(同步)调用耗时 */
.then(() => {
switch (type) {
case plugins_1.RegistryCategory.Instance: {
return this.updateInstance(namespace, service, fromLocal, localEntryMeta);
}
case plugins_1.RegistryCategory.Rule: {
return this.updateRule(namespace, service, fromLocal, localEntryMeta);
}
case plugins_1.RegistryCategory.Ratelimit: {
return this.updateRatelimit(namespace, service, fromLocal, localEntryMeta);
}
default: {
(0, utils_1.UNREACHABLE)();
}
}
})
.then((hasUpdated) => {
if (localEntryMeta.status !== EntryStatus.Released) {
localEntryMeta.status = EntryStatus.Normal;
localEntryMeta.outdatedTime = undefined;
this.attachUpdaterToEntryMeta(type, localEntryMeta);
}
return hasUpdated;
}, (err) => {
if (localEntryMeta.status !== EntryStatus.Released) {
/** 当请求失败时,不更新过期时间戳 `outdatedTime` */
localEntryMeta.status = EntryStatus.Outdated;
}
throw err;
});
}
async updateInstance(namespace, service, fromLocal, entryMeta) {
const fromNaming = await this.request(plugins_1.RegistryCategory.Instance, namespace, service, fromLocal.revision);
if (entryMeta.status === EntryStatus.Released || fromNaming.revision === fromLocal.revision) {
return false;
}
fromNaming.data.forEach((namingInstance) => {
const exists = fromLocal.data.some((localInstance) => {
if (namingInstance.id === localInstance.id) {
(0, instance_1.instanceCopy)(namingInstance, localInstance);
if (namingInstance.status !== localInstance.status) {
/** EventEmitter Synchronously calls each of the listeners */
this.emit("SyncInstanceStatus" /* SyncInstanceStatus */, namespace, service, localInstance, namingInstance.status);
localInstance.status = namingInstance.status;
}
return true;
}
return false;
});
if (!exists) {
fromLocal.data.push(namingInstance);
}
});
fromLocal.data = fromLocal.data.filter(localInstance => fromNaming.data.some(namingInstance => namingInstance.id === localInstance.id));
fromLocal.revision = fromNaming.revision;
this.registry[plugins_1.RegistryCategory.Instance].set(namespace, service, fromLocal);
this.changelog(plugins_1.RegistryCategory.Instance, namespace, service, fromNaming.revision);
return true;
}
async updateRule(namespace, service, fromLocal, entryMeta) {
const fromNaming = await this.request(plugins_1.RegistryCategory.Rule, namespace, service, fromLocal.revision);
if (entryMeta.status === EntryStatus.Released || fromNaming.revision === fromLocal.revision) {
return false;
}
this.registry[plugins_1.RegistryCategory.Rule].set(namespace, service, fromNaming);
this.changelog(plugins_1.RegistryCategory.Rule, namespace, service, fromNaming.revision);
return true;
}
async updateRatelimit(namespace, service, fromLocal, entryMeta) {
const fromNaming = await this.request(plugins_1.RegistryCategory.Ratelimit, namespace, service, fromLocal.revision);
if (entryMeta.status === EntryStatus.Released || fromNaming.revision === fromLocal.revision) {
return false;
}
fromNaming.data.forEach((namingRule) => {
const localData = fromLocal.data;
const { length } = localData;
let index = 0;
for (; index < length; index += 1) {
const localRule = localData[index];
if (namingRule.id === localRule.id) {
if (namingRule.revision !== localRule.revision) {
localData[index] = namingRule;
}
break;
}
}
if (index === length) { /** not found */
fromLocal.data.push(namingRule);
}
});
fromLocal.data = fromLocal.data.filter(localRule => fromNaming.data.some(namingRule => namingRule.id === localRule.id));
fromLocal.revision = fromNaming.revision;
this.registry[plugins_1.RegistryCategory.Ratelimit].set(namespace, service, fromNaming);
this.changelog(plugins_1.RegistryCategory.Ratelimit, namespace, service, fromNaming.revision);
return true;
}
async request(type, namespace, service, revision) {
const key = `${type}#${namespace}.${service}${revision ? `@${revision}` : ""}`;
let activeRequest = this.activeRequestsPool[key];
const performRequest = !activeRequest;
if (performRequest) {
/**
* 当值为 `undefined` 时,说明对应(key)的槽未被使用,
* 故存储对象容量大小增加 1 个槽位。
*/
if (activeRequest === undefined) {
this.activeRequestsCapacity += 1;
}
this.activeRequestsCount += 1;
switch (type) {
case plugins_1.RegistryCategory.Instance: {
activeRequest = this.naming.list(namespace, service, revision);
break;
}
case plugins_1.RegistryCategory.Rule: {
activeRequest = this.naming.routingRules(namespace, service, revision);
break;
}
case plugins_1.RegistryCategory.Ratelimit: {
if (!this.ratelimit) {
(0, utils_1.UNREACHABLE)();
}
activeRequest = this.ratelimit.ratelimitRules(namespace, service, revision);
break;
}
default: {
(0, utils_1.UNREACHABLE)();
}
}
this.activeRequestsPool[key] = activeRequest;
}
let result;
try {
result = await activeRequest;
}
finally {
if (performRequest) {
this.activeRequestsCount -= 1;
}
/**
* 回收机制:
* 当超过最大容量且当前没有未完成的请求时,进行整体回收(重新创建)。
* 这里为了性能考虑这里不使用 `delete` 删除对象属性,避免退化为字典模式。
*/
if (this.activeRequestsCount === 0 && this.activeRequestsCapacity > this.options.maxRequestsCapacity) {
this.activeRequestsPool = Object.create(null);
this.activeRequestsCapacity = 0;
}
/**
* 将使用过的槽设置成为 `null`,以便于复用时区分。
*/
this.activeRequestsPool[key] = null;
}
return result;
}
async pull(type, namespace, service) {
const { data: fromNaming, revision } = await this.request(type, namespace, service);
if (this.local(type, namespace, service) === null) {
this.registry[type].set(namespace, service, {
data: fromNaming,
revision
});
this.createEntryMeta(type, namespace, service, false);
return fromNaming;
}
return null;
}
// #endregion
// #region stat
report() {
const { historyRecorder } = this;
this.historyRecorder = Object.create(null);
this.historyRecorderCount = 0;
Object.keys(historyRecorder).forEach((namespace) => {
const serviceHistory = historyRecorder[namespace];
Object.keys(serviceHistory).forEach((service) => {
var _a, _b, _c;
const changelog = serviceHistory[service];
const rules = (_a = this.registry[plugins_1.RegistryCategory.Rule].get(namespace, service)) === null || _a === void 0 ? void 0 : _a.data;
const stat = {
[plugins_1.RegistryCategory.Instance]: {
history: changelog[plugins_1.RegistryCategory.Instance],
/*
* 当前实例列表不存在或为空,则认为当前已删除
*/
eliminated: (((_b = this.registry[plugins_1.RegistryCategory.Instance].get(namespace, service)) === null || _b === void 0 ? void 0 : _b.data.length) || 0) === 0
},
[plugins_1.RegistryCategory.Rule]: {
history: changelog[plugins_1.RegistryCategory.Rule],
/*
* 当前规则不存在或出入规则均为空,则认为当前已删除
*/
eliminated: ((rules === null || rules === void 0 ? void 0 : rules.in.length) || 0) === 0 && ((rules === null || rules === void 0 ? void 0 : rules.out.length) || 0) === 0
},
[plugins_1.RegistryCategory.Ratelimit]: {
history: changelog[plugins_1.RegistryCategory.Ratelimit],
/*
* 当前限流规则不存在或为空,则认为当前已删除
*/
eliminated: (((_c = this.registry[plugins_1.RegistryCategory.Ratelimit].get(namespace, service)) === null || _c === void 0 ? void 0 : _c.data.length) || 0) === 0
}
};
this.reporters.forEach((reporter) => {
var _a;
(_a = reporter.registryCache) === null || _a === void 0 ? void 0 : _a.call(reporter, namespace, service, stat).catch(err => this.disposed || this.logger.error(`[${reporter.name}] [registryCache]`, err));
});
});
});
}
changelog(type, namespace, service, revision) {
if (!this.needReport) {
return;
}
const log = {
time: Date.now(),
revision
};
let ns = this.historyRecorder[namespace];
if (!ns) {
ns = Object.create(null);
this.historyRecorder[namespace] = ns;
}
let svr = ns[service];
if (!svr) {
svr = {
[plugins_1.RegistryCategory.Instance]: [],
[plugins_1.RegistryCategory.Rule]: [],
[plugins_1.RegistryCategory.Ratelimit]: []
};
ns[service] = svr;
}
svr[type].push(log);
this.historyRecorderCount += 1;
if (this.historyRecorderCount >= this.options.reportThreshold) {
this.cancelHistoryReporter();
this.report();
}
else if (this.historyReporter === undefined) {
this.historyReporter = setTimeout(() => {
this.report();
this.historyReporter = undefined;
}, this.options.reportInterval).unref();
}
}
cancelHistoryReporter() {
if (this.historyReporter) {
clearTimeout(this.historyReporter);
this.historyReporter = undefined;
}
}
// #endregion
// #region ServiceMeta
accessService(namespace, service) {
const { recycleTime } = this.options;
if (!(recycleTime > 0) || recycleTime === Infinity) {
return;
}
const serviceMeta = this.servicesMeta[`${namespace}.${service}`];
if (serviceMeta === undefined) {
this.createServiceMeta(namespace, service);
}
else {
serviceMeta.accessed = true;
}
}
dropServiceMeta(namespace, service) {
if (namespace === undefined && service === undefined) {
Object.values(this.servicesMeta)
.forEach(({ garbageCollector }) => clearInterval(garbageCollector));
return true;
}
if (namespace === undefined || service === undefined) {
(0, utils_1.UNREACHABLE)();
}
const serviceMeta = this.servicesMeta[`${namespace}.${service}`];
if (serviceMeta) {
clearInterval(serviceMeta.garbageCollector);
delete this.servicesMeta[`${namespace}.${service}`];
return true;
}
return false;
}
createServiceMeta(namespace, service) {
const { recycleTime } = this.options;
const serviceMeta = {
accessed: true,
garbageCollector: setInterval(() => {
if (serviceMeta.accessed) {
serviceMeta.accessed = false;
}
else {
this.dropServiceMeta(namespace, service);
this.dropService([plugins_1.RegistryCategory.Instance, plugins_1.RegistryCategory.Ratelimit, plugins_1.RegistryCategory.Rule], namespace, service);
}
}, recycleTime).unref()
};
this.servicesMeta[`${namespace}.${service}`] = serviceMeta;
return serviceMeta;
}
dropService(types, namespace, service) {
types.forEach((type) => {
this.dropEntryMeta(type, namespace, service);
this.registry[type].delete(namespace, service);
});
}
// #endregion
// #region EntryMeta
attachRefresherToEntryMeta(type, entryMeta, namespace, service) {
const interval = this.options.refreshTime[type];
if (!(interval > 0) || interval === Infinity) {
return;
}
entryMeta.contentRefresher = setInterval(() => {
if (entryMeta.status === EntryStatus.Outdated) {
this.performUpdate(type, namespace, service).catch(e => this.logger.error(`[Registry] [refresher], refresh ${namespace}.${service}#${type}`, e));
}
}, interval).unref();
}
attachUpdaterToEntryMeta(type, entryMeta) {
const timeout = this.options.expireTime[type];
if (!(timeout > 0) || timeout === Infinity) {
return;
}
if (entryMeta.statusUpdater !== null) {
clearTimeout(entryMeta.statusUpdater);
entryMeta.statusUpdater = null;
}
entryMeta.statusUpdater = setTimeout(() => {
entryMeta.statusUpdater = null;
/*
* note:
* 请注意,这里必须要判断当前状态是否正常,
* 否则会造成异步死循环问题
*/
if (entryMeta.status === EntryStatus.Normal) {
entryMeta.status = EntryStatus.Outdated;
entryMeta.outdatedTime = (0, process_1.uptime)();
}
}, timeout).unref();
}
releaseEntryMeta(entryMeta) {
entryMeta.status = EntryStatus.Released;
entryMeta.outdatedTime = undefined;
if (entryMeta.statusUpdater !== null) {
clearTimeout(entryMeta.statusUpdater);
entryMeta.statusUpdater = null;
}
if (entryMeta.contentRefresher !== null) {
clearInterval(entryMeta.contentRefresher);
entryMeta.contentRefresher = null;
}
}
dropEntryMeta(type, namespace, service) {
/** drop all */
if (type === undefined && namespace === undefined && service === undefined) {
Object.values(this.entriesMeta).forEach((entriesMeta) => {
Object.values(entriesMeta).forEach((entryMeta) => {
this.releaseEntryMeta(entryMeta);
});
});
this.entriesMeta = this.createEntries();
return true;
}
/** drop one */
const entryMeta = this.getEntryMeta(type, namespace, service, true);
if (entryMeta !== undefined) {
this.releaseEntryMeta(entryMeta);
return true;
}
return false;
}
createEntries() {
return {
[plugins_1.RegistryCategory.Instance]: Object.create(null),
[plugins_1.RegistryCategory.Rule]: Object.create(null),
[plugins_1.RegistryCategory.Ratelimit]: Object.create(null)
};
}
createEntryMeta(type, namespace, service, isOutdated) {
const entryMeta = {
status: isOutdated ? EntryStatus.Outdated : EntryStatus.Normal,
statusUpdater: null,
contentRefresher: null
};
if (isOutdated) {
entryMeta.outdatedTime = (0, process_1.uptime)();
}
else {
this.attachUpdaterToEntryMeta(type, entryMeta);
}
this.attachRefresherToEntryMeta(type, entryMeta, namespace, service);
this.entriesMeta[type][`${namespace}.${service}`] = entryMeta;
return entryMeta;
}
getEntryMeta(type, namespace, service, popup = false) {
const entriesMeta = this.entriesMeta[type];
let entryMeta = entriesMeta[`${namespace}.${service}`];
if (entryMeta === undefined) {
if (!popup) {
/**
* 在本地名字服务插件中找得到的数据,并不一定有其对应的 `EntryMeta`,
* 故如查不到则创建,并标注为已过期 `EntryStatus.Outdated`,以便尽快更新
*/
entryMeta = this.createEntryMeta(type, namespace, service, true);
}
}
else if (popup) {
delete entriesMeta[`${namespace}.${service}`];
}
return entryMeta;
}
}
exports.LocalRegistry = LocalRegistry;
//# sourceMappingURL=registry.js.map