tencentcloud-edgeone-migration-nodejs-v2
Version:
tencentcloud cdn config copy to edgeone
398 lines • 18.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PolarisBaseAdapter = void 0;
const process_1 = require("process");
const __1 = require("../../../..");
const location_1 = require("../../../../location");
const plugins_1 = require("../../../../plugins");
const utils_1 = require("../../../../utils");
const pool_1 = require("../pool");
const types_1 = require("../types");
const utils_2 = require("../utils");
const kErrorLevel = 300000;
const kExceptionLevel = 500000;
const kServiceNotFoundLevel = 400000;
/**
* 由于 class ... implements ... 中 class 必须要实现 implements 中的接口,
* 并且 `Mixin` 方法无法组合(继承)类型,故在此仅实现 `Boxable` 接口
*/
class PolarisBaseAdapter {
constructor(remotes) {
this.remotes = remotes;
this.mode = plugins_1.OperatingMode.Internal;
this.pool = new pool_1.ClientPool(this.buildClient.bind(this), this.options);
this.disposeFuncs = Object.create(null);
this.disposed = false;
this.initializeStatus = {
promise: null,
initialized: false /** fast case */
};
this.loc = Object.assign({}, location_1.blankLocation /** copy */);
/**
* (empty constructor)
*/
}
setLogger(logger) {
this.logger || (this.logger = logger);
}
get location() {
return this.loc;
}
// #region dispose
async dispose() {
var _a, _b;
const { initializeStatus, logger } = this;
/** 等待初始化完成后再销毁 */
if (!initializeStatus.initialized && initializeStatus.promise) {
try {
await initializeStatus.promise;
}
catch (e) {
/**
* 当初始化发生异常时,会回滚 `bootstrap()` 已创建对象,
* 由于 `mainConsumer` 复用了当前的插件实例,
* 从而导致当前实例的 `dispose` 方法也会被调用,而当前实例并不能被释放。
* 故初始化发生异常时,直接返回不做任何处理。
*/
return;
}
}
logger.trace("Plugins" /* Plugins */, this.name, "dispose", undefined, "started");
this.disposed = true;
Object.values(this.disposeFuncs).forEach(func => func());
this.pool.dispose();
(_a = this.mainConsumer) === null || _a === void 0 ? void 0 : _a.dispose();
(_b = this.stickyConsumer) === null || _b === void 0 ? void 0 : _b.dispose();
logger.trace("Plugins" /* Plugins */, this.name, "dispose", undefined, "dispose completed");
}
get isDisposed() {
return this.disposed;
}
// #endregion
// #region request
/**
* 由于 `stickyConsumer` 仅用于少数调用,
* 故在一般情况下(如:仅使用 Consumer 时)不会被调用,
* 在这里做延迟初始化以节省内存开销
*/
buildStickyConsumer() {
if (this.stickyConsumer === undefined) {
this.stickyConsumer = new __1.InternalConsumer({
[plugins_1.PluginType.NamingService]: this,
[plugins_1.PluginType.LocalRegistry]: this.localRegistry,
[plugins_1.PluginType.LoadBalancer]: new __1.HashRingLoadBalancer()
}, undefined, this.logger);
}
return this.stickyConsumer;
}
async selectBackend(consumer, service, key) {
if (this.disposed) {
throw new __1.StateError("Already disposed");
}
const { protocol, logger } = this;
const caller = {
namespace: "",
service: "",
metadata: {
protocol
}
};
const callee = {
namespace: this.options.polarisNamespace,
service,
metadata: {
protocol
}
};
const { transaction } = logger;
logger.trace("Plugins" /* Plugins */, this.name, "selectBackend", transaction, "query service is", service);
const response = await consumer.select(callee, caller, {
[plugins_1.PluginType.LoadBalancer]: key
});
if (response) {
if (logger.tracingEnabled) {
const { instance: { host, port } } = response;
logger.trace("Plugins" /* Plugins */, this.name, "selectBackend", transaction, `${service} backend is ${host}:${port}`);
}
return response;
}
logger.trace("Plugins" /* Plugins */, this.name, "selectBackend", transaction, `query ${service} failed`);
/**
* 在某些特定场景下,后端不一定实现了全量服务,故在此采用 ServiceNotFound 异常
*/
throw new __1.ServiceNotFound(`${callee.namespace}.${service} not found`);
}
/**
* 此函数签名用于实现
*
* @note
* TS 参数需满足双向协变要求,故需将 |payload| 由联合类型 (Union Type) 转为交集类型 (Intersection Type),
* 但由于 TS 所限,如使用泛型类型 `UnionToIntersection<T>` 转换,|payload| 会推导出 `unkown` 类型。
* 鉴于上述问题,做出如下处理:
* * 在实现时,采用明确类型签名;
* * 在调用时,提供泛型签名;
*/
async requestBackend(type, service, method, payload, key) {
const calledInstances = [];
let lastError;
const { logger } = this;
const { transaction } = logger;
if (logger.tracingEnabled) {
logger.trace("Plugins" /* Plugins */, this.name, "requestBackend", transaction, `request ${service}#${method} with`, payload, key ? `by ${key}` : "");
}
for (;;) {
let comsumer = this.mainConsumer;
if (typeof key === "string") {
comsumer = this.buildStickyConsumer();
}
/**
* 当在初始化过程中发生穿透调用(访问 `polarisNamespace.discoverService`)时,
* `consumer` 有可能不存在(由于初始化失败被释放),
* 则直接抛出上次调用异常,不再进行重试。
*/
if (comsumer === undefined) {
/**
* 当 `consumer` 不存在时,`lastError` 一定存在。
* _也就是说:至少有一次调用_
*/
if (lastError === undefined) {
(0, utils_1.UNREACHABLE)();
}
throw lastError;
}
const instanceResponse = await this.selectBackend(comsumer, service, key);
const { instance, callee } = instanceResponse;
if (calledInstances.includes(instance)) {
throw new __1.NetworkError(lastError);
}
/**
* 如目标实例为预置实例,则上报被调名增加特定后缀
*/
if (instance.version === utils_2.kInstanceLocalVersion) {
callee.service += this.options.presetSuffix;
}
calledInstances.push(instance);
if (logger.tracingEnabled) {
const { host, port } = instance;
logger.trace("Plugins" /* Plugins */, this.name, "requestBackend", transaction, `call ${host}:${port}`);
}
const startTime = (0, process_1.uptime)();
let callResponse;
try {
callResponse = await this.pool.getOrCreateClient(instance, type)[method](payload);
}
catch (e) {
instanceResponse.update(false, ((0, process_1.uptime)() - startTime) * utils_1.kSeconds);
const { host, port } = instance;
logger.error(`[${this.name}] [requestBackend] call ${this.options.polarisNamespace}.${service}#${method}(${host}:${port}) exception`, e);
logger.trace("Plugins" /* Plugins */, this.name, "requestBackend", transaction, "failed with err", e);
lastError = e;
continue;
}
const code = this.unbox(callResponse.code, NaN);
instanceResponse.update(true, ((0, process_1.uptime)() - startTime) * utils_1.kSeconds, `${code || 0}`);
logger.trace("Plugins" /* Plugins */, this.name, "requestBackend", transaction, "finished with code", code);
return callResponse;
}
}
/**
* 追踪后端调用结果
* @param transaction 追踪 ID
* @param method 调用函数名
* @param response 后端响应
*/
tracingResponse(transaction, method, response) {
const { logger } = this;
if (logger.tracingEnabled) {
const code = this.unbox(response.code, NaN);
const info = this.unbox(response.info, "");
logger.trace("Plugins" /* Plugins */, this.name, method, transaction, `response code is ${code} and with info: ${info}`);
}
}
/**
* 判断后端调用是否成功(不成功则抛出异常)
* @param response 后端响应
* @param namespace 命名空间
* @param service 服务名
*/
maybeErrorResponse(response, namespace, service) {
const code = this.unbox(response.code, NaN);
const info = this.unbox(response.info, "");
if (Number.isNaN(code)) {
throw new __1.InvalidResponse("Illegal response");
}
if (code >= kErrorLevel) {
const message = `[${code}] [${namespace}.${service}] ${info}`;
if (code >= kExceptionLevel) {
throw new __1.ServerException(message);
}
if (code >= kServiceNotFoundLevel) {
throw new __1.ServiceNotFound(message);
}
throw new __1.ServerError(message);
}
}
/**
* 判断后端调用是否成功
* @param response 后端响应
* @param level 比较级别(默认为 `kErrorLevel`)
*/
isSuccessResponse(response, level = kErrorLevel) {
return this.unbox(response.code, NaN) < level;
}
// #endregion
// #region initialize
async waitForInitialized() {
const { initializeStatus, logger } = this;
const { transaction } = logger;
if (initializeStatus.promise === null) {
/*
* note:
* 这里必须先创建 Promise {<pending>},
* 避免 `bootstrap()` 未返回时又进行调用造成死循环
*/
let resolve;
let reject;
initializeStatus.promise = new Promise((...args) => {
[resolve, reject] = args;
});
logger.trace("Plugins" /* Plugins */, this.name, "waitForInitialized", transaction, "start bootstrap");
this.bootstrap().then(resolve, (err) => {
var _a;
/** 回滚 `bootstrap()` 已创建对象 */
(_a = this.mainConsumer) === null || _a === void 0 ? void 0 : _a.dispose(true);
initializeStatus.promise = null;
this.localRegistry = undefined;
this.local = undefined;
this.loc = Object.assign({}, location_1.blankLocation);
this.mainConsumer = undefined;
reject(err);
});
}
else {
logger.trace("Plugins" /* Plugins */, this.name, "waitForInitialized", transaction, "waiting for latest bootstrap function execution complete");
}
try {
await initializeStatus.promise;
}
catch (e) {
logger.trace("Plugins" /* Plugins */, this.name, "waitForInitialized", transaction, "bootstrap with exception", e);
throw e;
}
initializeStatus.initialized = true;
logger.trace("Plugins" /* Plugins */, this.name, "waitForInitialized", transaction, "bootstrap success");
}
// eslint-disable-next-line max-lines-per-function
async bootstrap() {
const { logger, protocol, options: { polarisNamespace, discoverService, detectionTimeout, bootstrapOnly } } = this;
const { transaction } = logger;
// #region 构建 Consumer
/*
* 在启动时,通过配置(北极星实例与规则路由)打底缓存,断路调用链
* _也就是说,启动时通过 `mainConsumer` 查询北极星远端实例,不再调用此插件_
* 这样做归一化了启动流程,其它接口无需再做区分(是否为启动流程)
*/
this.localRegistry = new __1.MemoryOnlyRegistry();
this.localRegistry[plugins_1.RegistryCategory.Instance].set(polarisNamespace, discoverService, {
revision: "",
data: (0, utils_2.address2Instance)(this.remotes, this.protocol)
});
this.localRegistry[plugins_1.RegistryCategory.Rule].set(polarisNamespace, discoverService, {
data: {
in: [{
sources: [{ service: "*", metadata: { protocol } }],
destinations: [{ service: "*", priority: 0, weight: 1, metadata: { protocol } }]
}],
out: []
},
revision: ""
});
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "initialized LocalRegistryPlugin with address", this.remotes);
const mainConsumer = new __1.InternalConsumer({
[plugins_1.PluginType.NamingService]: this,
[plugins_1.PluginType.LocalRegistry]: this.localRegistry,
[plugins_1.PluginType.CircuitBreaker]: new __1.PolarisCircuitBreaker({
continuousErrors: 1 /** 由于调用量低,故降低阈值,快速熔断 */
}),
[plugins_1.PluginType.LoadBalancer]: new __1.StickyLoadBalancer(new __1.WRLoadBalancer(), {
expireTime: this.options.switchDuration
})
}, {
/** 北极星后端变更极少,降低刷新时间,以优化后端压力 */
refreshTime: {
[plugins_1.RegistryCategory.Instance]: 1 * utils_1.kHours,
[plugins_1.RegistryCategory.Ratelimit]: 1 * utils_1.kHours
},
/** 配置实例与规则的脏数据时间为无穷大,避免死循环 */
dirtyTime: {
[plugins_1.RegistryCategory.Instance]: Infinity,
[plugins_1.RegistryCategory.Rule]: Infinity
},
recycleTime: Infinity
}, logger);
this.mainConsumer = mainConsumer;
// #endregion
// #region 更新 Discover Service 远端
let parallelTask = [];
if (bootstrapOnly) {
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "update discover remote");
parallelTask = [
mainConsumer.update(polarisNamespace, discoverService, plugins_1.RegistryCategory.Rule),
mainConsumer.update(polarisNamespace, discoverService, plugins_1.RegistryCategory.Instance)
];
}
// #endregion
// #region 获取本地对应的 IP 地址
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "fetch local address");
parallelTask.push((0, utils_2.fastestRemote)(this.remotes.map(remote => (0, utils_2.address2Endpoint)(remote)), detectionTimeout));
// #endregion
// #region 等待所有异步任务完成
const taskResult = await Promise.all(parallelTask);
if (bootstrapOnly) {
this.pool.truncate(); // 远端实例更新完成时,断开所有引导实例
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "discover remote update completed");
}
this.local = taskResult[taskResult.length - 1].local;
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "local adddress is", this.local.host);
// #endregion
// #region 获得客户端 `Location`
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "locate client location");
let client;
for (let i = 0; i < this.remotes.length; i += 1) { // eslint-disable-line @typescript-eslint/prefer-for-of
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "querying", this.remotes[i]);
const response = await this.requestBackend(types_1.ServiceType.Discover, discoverService, "reportClient", {
host: (0, utils_2.isAnyAddr)(this.local.host) ? null : this.box(this.local.host),
version: this.box(utils_1.kModuleVersion),
type: types_1.ClientType.SDK
});
if (this.isSuccessResponse(response)) {
({ client } = response);
}
if (this.isSuccessResponse(response, kExceptionLevel)) {
break;
}
else if (logger.tracingEnabled) {
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "exception for locate location, response code is", this.unbox(response.code, NaN));
}
}
logger.trace("Plugins" /* Plugins */, this.name, "bootstrap", transaction, "client location is", client === null || client === void 0 ? void 0 : client.location);
/*
* note:
* 当得不到 `location` 信息时,不走就近调用相关逻辑
*/
if (client === null || client === void 0 ? void 0 : client.location) {
const { location: { campus, region, zone } } = client;
this.loc = {
campus: this.unbox(campus, ""),
region: this.unbox(region, ""),
zone: this.unbox(zone, "")
};
}
else {
logger.info(`[${this.name}] [bootstrap], client location not found`);
}
// #endregion
}
}
exports.PolarisBaseAdapter = PolarisBaseAdapter;
//# sourceMappingURL=base.js.map