UNPKG

consul-balancer

Version:
261 lines (260 loc) 10.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConsulBalancer = void 0; const os = __importStar(require("os")); const consul_1 = __importDefault(require("consul")); const urllib = __importStar(require("urllib")); const grpc = __importStar(require("@grpc/grpc-js")); const lodash_1 = require("./utils/lodash"); class ConsulBalancer { constructor(arg1, arg2, arg3) { this.cachedServices = new Map(); this.conculInstance = null; this.options = this.parseOptions(arg1, arg2, arg3); this.address = this.findFirstNonLoopbackHostInfo(); this.serviceRegisterId = this.getServiceRegisterId(); if (this.options.discovery.register) { this.register(); } } getConsulInstance() { if (!this.conculInstance) { const consulOptions = (0, lodash_1.omit)(this.options, 'discovery'); this.conculInstance = new consul_1.default(consulOptions); } return this.conculInstance; } // default register discovery service register(options = {}) { var _a; return __awaiter(this, void 0, void 0, function* () { const check = { interval: '15s', timeout: '10s', ttl: '60s', deregistercriticalserviceafter: '5m', }; if (this.options.discovery.healthCheckHTTP) { check.http = `http://${this.address}:${this.options.discovery.servicePort}${this.options.discovery.healthCheckHTTP}`; } else { check.tcp = `${this.address}:${this.options.discovery.servicePort}`; } if (this.options.gRPC) { (_a = options.meta) !== null && _a !== void 0 ? _a : (options.meta = {}); Object.assign(options.meta, { gRPC_port: this.options.gRPC.port }); } const registerOptions = (0, lodash_1.defaults)(options, { id: this.serviceRegisterId, name: this.options.discovery.serviceName, address: this.address, port: this.options.discovery.servicePort, tags: [this.options.discovery.serviceName], check, }); try { return yield this.getConsulInstance().agent.service.register(registerOptions); } catch (e) { console.error(`[${new Date()}] consul-balancer register ${e}`); throw e; } }); } // default deregister discovery service deregister(serviceId) { return this.getConsulInstance().agent.service.deregister(serviceId !== null && serviceId !== void 0 ? serviceId : this.serviceRegisterId); } /** * grpc load balancer * @param serviceName - name of the service | address:port * @param targe - grpc client instance target * @param method - grpc method name * @param data - grpc request data */ grpc(serviceName, target, method, data) { var _a; return __awaiter(this, void 0, void 0, function* () { let service = null; const [address, port] = serviceName.split(':'); if (address && port) { // directly use address:port service = { address, port }; } else { // Get random healthy service service = yield this.getPassingServiceByRandom(serviceName); if (!service) throw new Error(`${serviceName} 服务不可用`); if (!((_a = service.meta) === null || _a === void 0 ? void 0 : _a['gRPC_port'])) { throw new Error('gRPC service meta not found gRPC_port'); } service.port = +service.meta['gRPC_port']; } // Create client const grpcClient = new target(`${service.address}:${service.port}`, grpc.credentials.createInsecure()); console.log(`[${new Date()}] consul-balancer grpc ~ ${target.serviceName}#${method}`); return new Promise((ok, fail) => { grpcClient[method](data, (err, result) => { if (err) return fail(err); return ok(result); }); }); }); } rest(serviceName, pathName, options = {}) { var _a; return __awaiter(this, void 0, void 0, function* () { const service = yield this.getPassingServiceByRandom(serviceName); if (!service) throw new Error(`${serviceName} 服务不可用`); const { address, port } = service; const defaultOptions = { dataType: 'json' }; if (!((_a = options === null || options === void 0 ? void 0 : options.headers) === null || _a === void 0 ? void 0 : _a['content-type'])) { (0, lodash_1.set)(options, ['headers', 'content-type'], 'application/json'); } const { href } = new URL(pathName, `http://${address}:${port}`); console.log(`[${new Date()}] consul-balancer rest ~ ${href}`); return urllib.curl(href, Object.assign(Object.assign({}, defaultOptions), options)); }); } getPassingServiceByRandom(serviceName = this.options.discovery.serviceName) { return __awaiter(this, void 0, void 0, function* () { let services = yield this.getServiceListFromConsul(serviceName); if (!services.length) { const cache = this.cachedServices.get(this.options.discovery.serviceName); if (cache && Date.now() - 60 * 1000 < cache.cacheTime) { services = cache.services; } } if (services.length) return (0, lodash_1.sample)(services); return null; }); } getServiceListFromConsul(serviceName, passing = true) { return __awaiter(this, void 0, void 0, function* () { const serviceList = (yield this.getConsulInstance().health.service({ service: serviceName, passing, })); const services = (serviceList !== null && serviceList !== void 0 ? serviceList : []).map((service) => { const result = { node: service.Node.Node, address: service.Service.Address, port: service.Service.Port, meta: service.Service.Meta, status: '', }; service.Checks.some((check) => { if (check.ServiceName === this.options.discovery.serviceName) { result.status = check.Status; return true; } return false; }); return result; }); if (services.length) { this.cachedServices.set(this.options.discovery.serviceName, { services, cacheTime: Date.now(), }); } return services; }); } parseOptions(...args) { const options = {}; for (const arg of args) { if (arg === null || typeof arg === 'undefined') { continue; } if (typeof arg === 'object') { (0, lodash_1.defaults)(options, arg); } switch (typeof arg) { case 'object': (0, lodash_1.defaults)(options, arg); break; case 'number': options.port = arg; break; case 'string': options.host = arg; break; default: throw new Error(`[consul-balancer] Invalid argument ${arg}`); } } return (0, lodash_1.defaults)(options, ConsulBalancer.defaultOptions); } getServiceRegisterId() { return [ this.options.discovery.serviceName, this.address, this.options.discovery.servicePort, ].join('-'); } findFirstNonLoopbackHostInfo() { const netInfo = os.networkInterfaces(); for (const netName of Object.keys(netInfo)) { const net = netInfo[netName]; for (const addr of net !== null && net !== void 0 ? net : []) { if (addr.internal === false && addr.family === 'IPv4') { return addr.address; } } } return this.options.host; } } exports.ConsulBalancer = ConsulBalancer; ConsulBalancer.defaultOptions = { host: '127.0.0.1', port: 8500, secure: false, discovery: { enable: true, register: false, deregister: true, serviceName: '', servicePort: 8080, } };