From 90b3de33d8930b378f7a8df232012468ae4c4d53 Mon Sep 17 00:00:00 2001 From: flyingCrp Date: Sun, 13 Aug 2023 23:31:33 +0800 Subject: [PATCH 1/2] refactor(consul): consul's implementation was refactored (#3109) * refactor(consul): consul's implementation was refactored * test(del): Delete some unused files * test(comment): Merge test cases --- packages/consul/src/config/config.default.ts | 18 +- packages/consul/src/configuration.ts | 92 +----- packages/consul/src/consul.service.ts | 158 +++++++++ packages/consul/src/controller/consul.ts | 10 +- packages/consul/src/index.ts | 2 +- packages/consul/src/interface.ts | 161 ++++++---- packages/consul/src/lib/balancer.ts | 84 ----- packages/consul/src/lib/provider.ts | 29 -- packages/consul/src/service/balancer.ts | 22 -- .../base-app/src/config/config.default.ts | 35 +- .../fixtures/base-app/src/configuration.ts | 22 +- .../fixtures/base-app/src/controller/test.ts | 16 - packages/consul/test/fixtures/mock.data.ts | 36 ++- packages/consul/test/index.test.ts | 142 +++++---- packages/consul/test/mock.ts | 25 +- packages/consul/usage.md | 166 +++++++--- site/docs/extensions/consul.md | 301 ++++++++---------- 17 files changed, 662 insertions(+), 657 deletions(-) create mode 100644 packages/consul/src/consul.service.ts delete mode 100644 packages/consul/src/lib/balancer.ts delete mode 100644 packages/consul/src/lib/provider.ts delete mode 100644 packages/consul/src/service/balancer.ts delete mode 100644 packages/consul/test/fixtures/base-app/src/controller/test.ts diff --git a/packages/consul/src/config/config.default.ts b/packages/consul/src/config/config.default.ts index 71859972f893..61d425bd5556 100644 --- a/packages/consul/src/config/config.default.ts +++ b/packages/consul/src/config/config.default.ts @@ -1,17 +1,5 @@ +import { IConsulOptions } from '../interface'; + export default { - consul: { - provider: { - register: false, - host: '127.0.0.1', - port: 8500, - strategy: 'random', - }, - service: { - id: null, - name: null, - tags: [], - address: null, - port: 7001, - }, - }, + consul: {} as IConsulOptions, }; diff --git a/packages/consul/src/configuration.ts b/packages/consul/src/configuration.ts index 6bbcea807f80..d1861d6c1cba 100644 --- a/packages/consul/src/configuration.ts +++ b/packages/consul/src/configuration.ts @@ -1,15 +1,4 @@ -import { - Config, - Configuration, - ILifeCycle, - IMidwayApplication, - IMidwayContainer, -} from '@midwayjs/core'; -import { - IConsulProviderInfoOptions, - IConsulRegisterInfoOptions, -} from './interface'; -import { ConsulProvider } from './lib/provider'; +import { Configuration, ILifeCycle } from '@midwayjs/core'; import * as DefaultConfig from './config/config.default'; @Configuration({ @@ -20,81 +9,4 @@ import * as DefaultConfig from './config/config.default'; }, ], }) -export class ConsulConfiguration implements ILifeCycle { - /** - * 有关 consul server 的配置 - */ - @Config('consul.provider') - consulProviderConfig: IConsulProviderInfoOptions; - - /** - * 有关 service registry 注册的信息 - */ - @Config('consul.service') - consulRegisterConfig: IConsulRegisterInfoOptions; - - get consulProvider(): ConsulProvider { - const symbol = Symbol('consulProvider'); - this[symbol] = - this[symbol] || new ConsulProvider(this.consulProviderConfig); - return this[symbol]; - } - - /** - * 注册自己的条件 - * 由于环境的复杂性(多网卡、自动端口冲突) address 和 port 必须提供 - */ - get shouldBeRegisterMe(): boolean { - const { address, port } = this.consulRegisterConfig; - return this.consulProviderConfig.register && address.length > 0 && port > 0; - } - - /** - * 注册 consul 服务 - * @param container 容器 IoC - * @param app 应用 App - */ - async registerConsul( - container: IMidwayContainer, - app: IMidwayApplication - ): Promise { - const config = this.consulRegisterConfig; - const { address, port } = this.consulRegisterConfig; - // 把原始的 consul 对象注入到容器 - container.registerObject('consul:consul', this.consulProvider.getConsul()); - if (this.shouldBeRegisterMe) { - config.name = config.name || app.getProjectName(); - config.id = config.id || `${config.name}:${address}:${port}`; - if (!config.check && (config.check as any) !== false) { - config.check = ['egg', 'koa', 'express'].includes(app.getNamespace()) - ? { - http: `http://${address}:${port}/consul/health/self/check`, - interval: '3s', - } - : { - tcp: `${address}:${port}`, - interval: '3s', - }; - } - Object.assign(this.consulRegisterConfig, config); - await this.consulProvider.registerService(this.consulRegisterConfig); - } - } - - async onServerReady( - container: IMidwayContainer, - app?: IMidwayApplication - ): Promise { - await this.registerConsul(container, app); - } - - async onStop(): Promise { - if ( - this.consulProviderConfig.register && - this.consulProviderConfig.deregister - ) { - const { id } = this.consulRegisterConfig; - await this.consulProvider.deregisterService({ id }); - } - } -} +export class ConsulConfiguration implements ILifeCycle {} diff --git a/packages/consul/src/consul.service.ts b/packages/consul/src/consul.service.ts new file mode 100644 index 000000000000..11e412737536 --- /dev/null +++ b/packages/consul/src/consul.service.ts @@ -0,0 +1,158 @@ +import { + Autoload, + Config, + Destroy, + Init, + Inject, + MidwayError, + MidwayInformationService, + Provide, + Singleton, +} from '@midwayjs/core'; +import * as Consul from 'consul'; +import { IConsulOptions, IServiceHealth, IServiceNode } from './interface'; + +export class MidwayConsulError extends MidwayError { + constructor(message: string) { + super(message); + } +} + +@Provide() +@Autoload() +@Singleton() +export class ConsulService { + @Config('consul') + private config: IConsulOptions; + + @Inject() + private infoSrv: MidwayInformationService; + + serviceId: string; + + private instance: Consul.Consul; + + get consul(): Consul.Consul { + return this.instance; + } + + private selectRandomService(arr: Array) { + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr[Math.floor(Math.random() * arr.length)]; + } + + @Init() + //eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + private async autoRegister() { + try { + this.instance = new Consul(this.config.options); + const { register } = this.config; + if (register) { + const { id, name, port, address, check, checks } = register; + // 如果没有配置健康监测,则视为顶层为web主框架,同时使用内置http的/health为健康检查的接口 + if (!check && !checks?.length) { + register.check = { + http: `http://${address}:${port}/health`, + interval: '5s', + ttl: '30s', + }; + } + const serviceName = name || this.infoSrv.getPkg().name; + this.serviceId = id; + if (!this.serviceId) { + this.serviceId = `${serviceName}:${address}:${port}`; + } + Object.assign(register, { + id: this.serviceId, + name: serviceName, + }); + await this.instance.agent.service.register(register); + } + } catch (e) { + throw new MidwayConsulError(`Service startup failure: ${e.message}`); + } + } + + private async loadAllService( + options: Consul.Catalog.Service.NodesOptions + ): Promise> { + const services: Array = + await this.instance.catalog.service.nodes(options); + if (!services.length) { + throw new MidwayConsulError( + `no available service instance named ${options.service}` + ); + } + return services; + } + + /** + * Select an available service instance by name and datacenter + * @param {string} serviceName the service name + * @param {Consul.Catalog.Service.NodesOptions} options the NodesOptions + */ + async select( + serviceName: string, + options?: Omit + ) { + const checkOpt = options || {}; + let checkedArr: Array; + try { + checkedArr = await this.instance.health.checks({ + ...checkOpt, + service: serviceName, + }); + } catch (e) { + if (e?.response?.statusCode === 404) { + checkedArr = []; + } else { + throw new MidwayConsulError(e.message); + } + } + if (!checkedArr.length) { + throw new MidwayConsulError( + `no available service instance named ${serviceName}` + ); + } + const passed: Array = checkedArr.filter( + service => service.Status === 'passing' + ); + if (!passed.length) { + throw new MidwayConsulError( + `The health status of services ${serviceName} is abnormal` + ); + } + const opt = options || {}; + const allNodes = await this.loadAllService({ + ...opt, + service: serviceName, + }); + const matched = allNodes.filter(r => { + return passed.some(a => a.ServiceID === r.ServiceID); + }); + if (!matched.length) { + throw new MidwayConsulError( + `no available service instance named ${serviceName}` + ); + } + return this.selectRandomService(matched) as IServiceNode; + } + + @Destroy() + //eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + private async autoDeregister() { + try { + const { deregister } = this.config; + if (this.serviceId && deregister !== false) { + await this.instance?.agent.service.deregister(this.serviceId); + } + } catch (e) { + throw new MidwayConsulError(e.message); + } + } +} diff --git a/packages/consul/src/controller/consul.ts b/packages/consul/src/controller/consul.ts index d1facb5cb3cc..41a266b49eb8 100644 --- a/packages/consul/src/controller/consul.ts +++ b/packages/consul/src/controller/consul.ts @@ -1,11 +1,9 @@ import { Controller, Get } from '@midwayjs/core'; -@Controller('/consul') +@Controller('/', { ignoreGlobalPrefix: true }) export class ConsulController { - @Get('/health/self/check') - async healthCheck(): Promise { - return { - status: 'success', - }; + @Get('/health') + async healthCheck() { + return 'success'; } } diff --git a/packages/consul/src/index.ts b/packages/consul/src/index.ts index 6341b08d5c4a..5a1eb179eaef 100644 --- a/packages/consul/src/index.ts +++ b/packages/consul/src/index.ts @@ -1,4 +1,4 @@ export { ConsulConfiguration as Configuration } from './configuration'; export * from './controller/consul'; -export * from './service/balancer'; export * from './interface'; +export * from './consul.service'; diff --git a/packages/consul/src/interface.ts b/packages/consul/src/interface.ts index 5e660006980d..fde3227b6da6 100644 --- a/packages/consul/src/interface.ts +++ b/packages/consul/src/interface.ts @@ -1,83 +1,106 @@ import * as Consul from 'consul'; -import type { ConsulOptions } from 'consul'; -import RegisterOptions = Consul.Agent.Service.RegisterOptions; +import { ConsulOptions } from 'consul'; -export interface IServiceBalancer { +/** + * consul configuration of midwayjs + */ +export interface IConsulOptions { /** - * 根据服务名称选择实例 - * @param serviceName 注册的服务名称 - * @param passingOnly 只返回通过健康检查的实例,默认为 true + * The Consul Original Configuration + * + * @see [consuloptions]{@link https://github.com/silas/node-consul#consuloptions} */ - select(serviceName: string, passingOnly?: boolean): any | never; -} - -export interface IConsulBalancer { - /** - * 根绝策略返回负载均衡器 - * @param strategy 负载均衡策略 - */ - getServiceBalancer(strategy?: string): IServiceBalancer; -} - -export interface IConsulProviderInfoOptions extends ConsulOptions { - /** - * 本服务是否注册到 consul 服务器,默认是 NO 不会执行注册 - */ - register?: boolean; - + options: ConsulOptions; /** - * 应用正常关闭的额时候自动反注册,默认是 YES 会执行反注册 - * 如果 register=false 改参数无效 + * Automatic deregister,default is true */ - deregister?: boolean; - + deregister: boolean; /** - * 调用服务负载均衡的策略(default、random),默认是 random 随机 + * The Service Registers Original Configuration + * + *@see [consul.agent.service.register.options]{@link https://github.com/silas/node-consul#consulagentserviceregisteroptions} */ - strategy?: string; + register: Consul.Agent.Service.RegisterOptions; } -export interface IConsulRegisterInfoOptions extends RegisterOptions { - /** - * 注册 id 标识,默认是 name:address:port 的组合 - */ - id?: string; - - /** - * 服务名称 - */ - name: string; - - /** - * 服务地址 - */ - address: string; - - /** - * 服务端口 - */ - port: number; - - /** - * 服务标签 - */ - tags?: string[]; +/** + * service status information + * + *@see [health-list-checks-for-service]{@link https://developer.hashicorp.com/consul/api-docs/health#sample-response-1} + */ +export interface IServiceHealth { + Node: string; + CheckID: string; + Name: string; + Status: 'passing' | 'warning' | 'critical'; + Notes: string; + Output: string; + ServiceID: string; + ServiceName: string; + ServiceTags: any[]; + Type: string; + ExposedPort: number; + Definition: { [props: string]: any }; + CreateIndex: number; + ModifyIndex: number; +} - /** - * 健康检查配置,组件默认会配置一个(检查间隔是3秒),如果指定 check=false 则关闭 consul 健康检查 - */ - check?: { - tcp?: string; - http?: string; - script?: string; - interval?: string; - ttl?: string; - notes?: string; - status?: string; +/** + * List Nodes for Service + * @see [List Nodes for Service](https://developer.hashicorp.com/consul/api-docs/catalog#sample-response-3) + */ +export interface IServiceNode { + ID: string; + Node: string; + Address: string; + Datacenter: string; + TaggedAddresses: { + lan: string; + wan: string; + }; + NodeMeta: { [key: string]: string }; + CreateIndex: number; + ModifyIndex: number; + ServiceAddress: string; + ServiceEnableTagOverride: boolean; + ServiceID: string; + ServiceName: string; + ServicePort: number; + ServiceMeta: { [key: string]: string }; + ServiceTaggedAddresses: { + lan: { address: string; port: number }; + wan: { address: string; port: number }; }; + ServiceTags: Array; + ServiceProxy: { + DestinationServiceName: string; + DestinationServiceID: string; + LocalServiceAddress: string; + LocalServicePort: number; + Config: string; + Upstreams: string; + }; + ServiceConnect: { + Native: boolean; + Proxy: string; + }; + Namespace: string; } -export interface ConsulConfig { - provider?: IConsulProviderInfoOptions; - service?: IConsulRegisterInfoOptions; -} +// noinspection JSUnusedGlobalSymbols +/** + * @see [consul.kv.get]{@link https://github.com/silas/node-consul#consulkvgetoptions} + */ +export type TKvGet = { + CreateIndex: number; + ModifyIndex: number; + LockIndex: number; + Key: string; + Flags: number; + Value: string; +}; +// noinspection JSUnusedGlobalSymbols +/** + * @see [consul.kv.keys]{@link https://github.com/silas/node-consul#consulkvkeysoptions} + */ +export type TKvKeys = Array; diff --git a/packages/consul/src/lib/balancer.ts b/packages/consul/src/lib/balancer.ts deleted file mode 100644 index 500ef36d861e..000000000000 --- a/packages/consul/src/lib/balancer.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as Consul from 'consul'; -import { IServiceBalancer, IConsulBalancer } from '../interface'; - -abstract class Balancer implements IServiceBalancer { - protected constructor(protected consul: Consul.Consul) { - // - } - - async select(serviceName: string, passingOnly = true): Promise { - // throw new Error('not implemented'); - } - - async loadServices(serviceName: string, passingOnly = true): Promise { - if (passingOnly) return await this.loadPassing(serviceName); - return await this.loadAll(serviceName); - } - - private async loadAll(serviceName: string): Promise { - return (await this.consul.catalog.service.nodes(serviceName)) as any[]; - } - - private async loadPassing(serviceName: string): Promise { - const passingIds = []; - const checks = (await this.consul.health.checks(serviceName)) as any[]; - - // 健康检查通过的 - checks.forEach(check => { - if (check.Status === 'passing') { - passingIds.push(check.ServiceID); - } - }); - - const instances = await this.loadAll(serviceName); - return instances.filter(item => passingIds.includes(item.ServiceID)); - } -} - -class RandomBalancer extends Balancer { - constructor(consul: Consul.Consul) { - super(consul); - } - - async select(serviceName: string, passingOnly = true): Promise { - let instances = await this.loadServices(serviceName, passingOnly); - if (instances.length > 0) { - instances = this.shuffle(instances); - return instances[Math.floor(Math.random() * instances.length)]; - } - - throw new Error('no available instance named ' + serviceName); - } - - /** - * shuff by fisher-yates - */ - shuffle(instances: Array): Array { - const result = []; - - for (let i = 0; i < instances.length; i++) { - const j = Math.floor(Math.random() * (i + 1)); - if (j !== i) { - result[i] = result[j]; - } - result[j] = instances[i]; - } - - return result; - } -} - -export class ConsulBalancer implements IConsulBalancer { - constructor(private consul: Consul.Consul) { - // - } - - getServiceBalancer(strategy?: string): IServiceBalancer { - switch (strategy) { - case 'random': - return new RandomBalancer(this.consul); - } - - throw new Error('invalid strategy balancer'); - } -} diff --git a/packages/consul/src/lib/provider.ts b/packages/consul/src/lib/provider.ts deleted file mode 100644 index 5f650d93f156..000000000000 --- a/packages/consul/src/lib/provider.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - IConsulProviderInfoOptions, - IConsulRegisterInfoOptions, -} from '../interface'; -import * as Consul from 'consul'; - -export class ConsulProvider { - private readonly consul: Consul.Consul; - - constructor(providerOptions: IConsulProviderInfoOptions) { - // should be, ignore config - providerOptions.promisify = true; - this.consul = new Consul(providerOptions); - } - - getConsul(): Consul.Consul { - return this.consul; - } - - async registerService( - registerOptions: IConsulRegisterInfoOptions - ): Promise { - await this.consul.agent.service.register(registerOptions); - } - - async deregisterService(deregisterOptions: { id: string }): Promise { - await this.consul.agent.service.deregister(deregisterOptions); - } -} diff --git a/packages/consul/src/service/balancer.ts b/packages/consul/src/service/balancer.ts deleted file mode 100644 index 10add5058710..000000000000 --- a/packages/consul/src/service/balancer.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import { ConsulBalancer } from '../lib/balancer'; -import * as Consul from 'consul'; -import { IServiceBalancer, IConsulBalancer } from '../interface'; - -@Provide() -@Scope(ScopeEnum.Singleton) -export class BalancerService implements IConsulBalancer { - @Inject('consul:consul') - consul: Consul.Consul; - - private consulBalancer: ConsulBalancer; - - @Init() - init(): void { - this.consulBalancer = new ConsulBalancer(this.consul); - } - - getServiceBalancer(strategy = 'random'): IServiceBalancer { - return this.consulBalancer.getServiceBalancer(strategy); - } -} diff --git a/packages/consul/test/fixtures/base-app/src/config/config.default.ts b/packages/consul/test/fixtures/base-app/src/config/config.default.ts index ca5ec748be90..8eac903ed2ee 100644 --- a/packages/consul/test/fixtures/base-app/src/config/config.default.ts +++ b/packages/consul/test/fixtures/base-app/src/config/config.default.ts @@ -1,20 +1,25 @@ +import { IConsulOptions } from '../../../../../src'; + export default { keys: 'midwayjs-consul-test', - + koa: { + port: 7001, + }, consul: { - provider: { - register: true, - deregister: true, - // see consul.framework.ts plz - host: 'mock.consul.server', - // host: '127.0.0.1', - port: 8500, - strategy: 'random', - }, - service: { + deregister: true, + register: { + name: 'consul-demo', address: '127.0.0.1', port: 7001, - tags: ["midwayjs-consul-test"], - } - } -} + dc: 'dc1', + }, + options: { + host: 'mock.consul.server', + port: '8500', + defaults: { + token: '123213', + }, + }, + } as IConsulOptions, +}; + diff --git a/packages/consul/test/fixtures/base-app/src/configuration.ts b/packages/consul/test/fixtures/base-app/src/configuration.ts index 26a67854a186..31fd2aef0363 100644 --- a/packages/consul/test/fixtures/base-app/src/configuration.ts +++ b/packages/consul/test/fixtures/base-app/src/configuration.ts @@ -1,24 +1,12 @@ -import { Configuration } from '@midwayjs/core'; +import { Configuration, ILifeCycle } from '@midwayjs/core'; import * as consul from '../../../../src'; import * as Koa from '@midwayjs/koa'; -import { - ILifeCycle, - IMidwayApplication, - IMidwayContainer, -} from '@midwayjs/core'; -import {join} from 'path'; +import { join } from 'path'; @Configuration({ - imports: [ - Koa, - consul - ], - importConfigs: [join(__dirname, 'config')] + imports: [Koa, consul], + importConfigs: [join(__dirname, 'config')], }) export class ContainerConfiguration implements ILifeCycle { - async onReady(container: IMidwayContainer, app?: IMidwayApplication) { - // console.info(app.getConfig()); - // const collector = new WebRouterCollector(); - // console.log(await collector.getFlattenRouterTable()); - } + async onReady() {} } diff --git a/packages/consul/test/fixtures/base-app/src/controller/test.ts b/packages/consul/test/fixtures/base-app/src/controller/test.ts deleted file mode 100644 index 7023b5421bfe..000000000000 --- a/packages/consul/test/fixtures/base-app/src/controller/test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Controller, Get, Inject, Param, Provide} from '@midwayjs/core'; -import {IConsulBalancer} from "../../../../../src"; - -@Provide() -@Controller('/test') -export class TestController { - @Inject('consul:balancerService') - balancerService: IConsulBalancer; - - // @ts-ignore - @Get('/balancer/lookup/:serviceName') - async lookupConsulService(@Param('serviceName') serviceName: string) { - const balancer = this.balancerService.getServiceBalancer(); - return await balancer.select(serviceName); - } -} diff --git a/packages/consul/test/fixtures/mock.data.ts b/packages/consul/test/fixtures/mock.data.ts index 20f75404c3c0..00d5ca5d8441 100644 --- a/packages/consul/test/fixtures/mock.data.ts +++ b/packages/consul/test/fixtures/mock.data.ts @@ -4,14 +4,14 @@ const services = [ Node: 'macpro.local', Address: '127.0.0.1', Datacenter: 'dc1', - TaggedAddresses: {lan: '127.0.0.1', wan: '127.0.0.1'}, - NodeMeta: {'consul-network-segment': ''}, + TaggedAddresses: { lan: '127.0.0.1', wan: '127.0.0.1' }, + NodeMeta: { 'consul-network-segment': '' }, ServiceKind: '', - ServiceID: 'ali-demo:127.0.0.1:7001', - ServiceName: 'ali-demo', + ServiceID: 'consul-demo:127.0.0.1:7001', + ServiceName: 'consul-demo', ServiceTags: ['midwayjs-consul-test'], ServiceAddress: '127.0.0.1', - ServiceWeights: {Passing: 2, Warning: 0}, + ServiceWeights: { Passing: 2, Warning: 0 }, ServiceMeta: {}, ServicePort: 7001, ServiceEnableTagOverride: false, @@ -19,25 +19,33 @@ const services = [ ServiceProxy: {}, ServiceConnect: {}, CreateIndex: 53, - ModifyIndex: 53 - } + ModifyIndex: 53, + }, ]; const checks = [ { Node: 'macpro.local', - CheckID: 'service:ali-demo:127.0.0.1:7001', - Name: "Service 'ali-demo' check", + CheckID: 'service:consul-demo:127.0.0.1:7001', + Name: "Service 'consul-demo' check", Status: 'passing', Notes: '', Output: '', - ServiceID: 'ali-demo:127.0.0.1:7001', - ServiceName: 'ali-demo', + ServiceID: 'consul-demo:127.0.0.1:7001', + ServiceName: 'consul-demo', ServiceTags: ['midwayjs-consul-test'], Definition: {}, CreateIndex: 83, - ModifyIndex: 83 - } + ModifyIndex: 83, + }, ]; +const agentService = { + 'consul-demo:127.0.0.1:7001': { + Service: 'consul-demo:127.0.0.1:7001', + Address: '127.0.0.1', + Port: 7001, + Datacenter: 'dc1', + }, +}; -export {services, checks}; +export { services, checks, agentService }; diff --git a/packages/consul/test/index.test.ts b/packages/consul/test/index.test.ts index 1dbeac4a01c1..bb8a144fd010 100644 --- a/packages/consul/test/index.test.ts +++ b/packages/consul/test/index.test.ts @@ -1,21 +1,26 @@ -import {close, createApp, createHttpRequest} from '@midwayjs/mock'; -import {IMidwayApplication} from '@midwayjs/core'; -import {IConsulBalancer} from '../src'; -import { mockConsulAPI } from './mock'; -import * as Consul from 'consul'; +import { close, createApp, createHttpRequest } from '@midwayjs/mock'; +import { IMidwayApplication } from '@midwayjs/core'; import { join } from 'path'; import * as nock from 'nock'; +import { mockConsulAPI } from './mock'; +import { ConsulService, IServiceNode, MidwayConsulError } from '../src'; -describe('/test/feature.test.ts', () => { +const serviceName = 'consul-demo'; - describe('test new features', () => { +describe('/test/consule.test', () => { + describe('test service', () => { + // 基于真实consul服务测试时请填写本地可用的地址和端口 + // const host = '192.168.101.114'; + // const port = 7001; + // const serviceId = `${serviceName}:${host}:${port}`; + const host = '127.0.0.1'; + const port = 7001; + const serviceId = `${serviceName}:${host}:${port}`; let app: IMidwayApplication; beforeAll(async () => { - // 如果使用真实的 server (consul agent --dev) 测试打开下面一行 - // 同时记得修改配置中的 consul.provide.host 参数 - // app = await createApp('base-app', {}, '@midwayjs/koa'); + // 真实测试时需要注销掉mock mockConsulAPI(); app = await createApp(join(__dirname, 'fixtures', 'base-app'), {}); }); @@ -25,69 +30,76 @@ describe('/test/feature.test.ts', () => { nock.cleanAll(); }); - it('should provide health check route', async () => { - const result = await createHttpRequest(app) - .get('/consul/health/self/check'); + it('should GET /health', async () => { + const result = await createHttpRequest(app).get('/health'); expect(result.status).toBe(200); - expect(JSON.parse(result.text).status).toBe('success'); + expect(result.text).toBe('success'); }); - - it('should get balancer from ioc container', async () => { - const balancerService = await app.getApplicationContext().getAsync('consul:balancerService'); - expect(balancerService).toBeDefined(); + it('should Register service', async function () { + const consulSrv = await app + .getApplicationContext() + .getAsync(ConsulService); + const result = consulSrv.serviceId; + expect(result).toBe(serviceId); }); - - it('should throw error when not imeplements balancer', async () => { - const balancerService = await app.getApplicationContext().getAsync('consul:balancerService'); + it('should throw MidwayConsulError when service unavailable', async function () { + const consulSrv = await app + .getApplicationContext() + .getAsync(ConsulService); try { - await balancerService.getServiceBalancer('noexists'); + await consulSrv.select('noexists'); + expect(true).toBe(false); } catch (e) { - expect(e).toBeDefined(); + expect(e).toBeInstanceOf(MidwayConsulError); } }); - - it('should throw error when lookup not exist service', async () => { - const balancerService = await app.getApplicationContext().getAsync('consul:balancerService'); - try { - await balancerService.getServiceBalancer().select('noexists'); - } catch (e) { - expect(e).toBeDefined(); - } + it('should Select Service', async function () { + const consulSrv = await app + .getApplicationContext() + .getAsync(ConsulService); + const result: IServiceNode = await new Promise((resolve, reject) => { + setTimeout(async () => { + try { + const result = await consulSrv.select(serviceName); + resolve(result); + } catch (e) { + reject(e); + } + }, 5000); + }); + expect(result.ServicePort).toBe(port); + expect(result.ServiceAddress || result.Address).toBe(host); }); - - it('should lookup consul service by name', async () => { - const balancerService = await app.getApplicationContext().getAsync('consul:balancerService'); - const service = await balancerService.getServiceBalancer().select(app.getProjectName(), false); - expect(service['ServiceAddress']).toBe('127.0.0.1'); - expect(service['ServicePort']).toBe(7001); - }); - - it('should lookup consul service which check-passing', async () => { - const balancerService = await app.getApplicationContext().getAsync('consul:balancerService'); - const service = await balancerService.getServiceBalancer().select(app.getProjectName()); - expect(service['ServiceAddress']).toBe('127.0.0.1'); - expect(service['ServicePort']).toBe(7001); + it('should Select Service with datacenter', async function () { + const consulSrv = await app + .getApplicationContext() + .getAsync(ConsulService); + const result: { pass: boolean; fail: boolean } = await new Promise( + resolve => { + setTimeout(async () => { + let result = { pass: false, fail: false }; + try { + const res = await consulSrv.select(serviceName, { + dc: 'dc1', + }); + result.pass = + res.ServicePort === port && + (res.ServiceAddress || res.Address) === host; + } catch (e) { + result.pass = false; + } + try { + await consulSrv.select(serviceName, { dc: 'invalid' }); + result.fail = false; + } catch (e) { + result.fail = true; + } + resolve(result); + }, 4000); + } + ); + expect(result.pass).toBe(true); + expect(result.fail).toBe(true); }); - - it('should lookup consul service by balancer which injected', async () => { - const result = await createHttpRequest(app) - .get(`/test/balancer/lookup/${app.getProjectName()}`); - expect(result.status).toBe(200); - const service = JSON.parse(result.text); - expect(service['ServiceAddress']).toBe('127.0.0.1'); - expect(service['ServicePort']).toBe(7001); - }); - - it('should get the origin consul object', async () => { - try { - const consul = await app.getApplicationContext().getAsync('consul:consul'); - expect(consul).toBeDefined(); - expect(consul).toBeInstanceOf(Consul); - } catch (e) { - expect(e).not.toBeInstanceOf(Error); - } - }); - }); - }); diff --git a/packages/consul/test/mock.ts b/packages/consul/test/mock.ts index 95122182f4d3..4278d55bdb3d 100644 --- a/packages/consul/test/mock.ts +++ b/packages/consul/test/mock.ts @@ -1,10 +1,10 @@ import * as nock from 'nock'; -import { checks, services } from './fixtures/mock.data'; +import { agentService, checks, services } from './fixtures/mock.data'; /** * 自定义实现 mock consul 相关的 http API */ -export function mockConsulAPI () { +export function mockConsulAPI() { // 断开外部 http 地址访问 nock.disableNetConnect(); // 允许 app#superTest 的路由访问 @@ -14,15 +14,28 @@ export function mockConsulAPI () { // 注册服务 nockObj.persist().put('/v1/agent/service/register').reply(200); + nockObj.persist().put('/v1/agent/service/register?dc=dc1').reply(200); // 反注册服务 - nockObj.persist().persist().put('/v1/agent/service/deregister/ali-demo%3A127.0.0.1%3A7001').reply(200); + nockObj + .persist() + .persist() + .put('/v1/agent/service/deregister/consul-demo%3A127.0.0.1%3A7001') + .reply(200); // 查询服务 - nockObj.persist().get('/v1/catalog/service/ali-demo').reply(200, services); - nockObj.persist().get('/v1/catalog/service/noexists').reply(200, []); + nockObj.persist().get('/v1/catalog/service/consul-demo').reply(200, services); + nockObj + .persist() + .get('/v1/catalog/service/consul-demo?dc=dc1') + .reply(200, services); + nockObj.persist().get('/v1/catalog/service').reply(200, agentService); // 健康检查 - nockObj.persist().get('/v1/health/checks/ali-demo').reply(200, checks); + nockObj.persist().get('/v1/health/checks/consul-demo').reply(200, checks); + nockObj + .persist() + .get('/v1/health/checks/consul-demo?dc=dc1') + .reply(200, checks); nockObj.persist().get('/v1/health/checks/noexists').reply(200, []); } diff --git a/packages/consul/usage.md b/packages/consul/usage.md index 8332fa2b1b3e..df073b7bf7cc 100644 --- a/packages/consul/usage.md +++ b/packages/consul/usage.md @@ -11,7 +11,6 @@ npm i @types/consul -D - [x] register (optional) - [x] deregister on the shutdown (optional) -- [x] service balancer (default random) - [x] expose the origin consul object ## Usage age @@ -28,57 +27,148 @@ import * as consul from '@midwayjs/consul' importConfigs: [join(__dirname, 'config')] }) export class ContainerConfiguration {} + ``` ### 2. config consul server and service definition. -``` -consul: { - provider: { - // 注册本服务 - register: true, - // 应用正常下线反注册 +```typescript +export default { + consul: { + // {Optional}, Default is undefined + // If you need to register your current application with consul;or you can also use it as a client without registering + // more parameter? here -> https://github.com/silas/node-consul#consulagentserviceregisteroptions + register: { + //{Required} + address: '127.0.0.1', + //{Required} + port: 7001, + //{Optional},Default to the name value of package.json + name: 'SERVICE_NAME', + //{Optional},Default is `name:address:port` + id: 'SERVICE_ID', + }, + // {Required},Configuration of consul,All parameters -> https://github.com/silas/node-consul#consuloptions, + options: { + host: '127.0.0.1', + port: '8500', + }, + //{Optional},Default is true;When value true is yes,will deregister the service after the application stops deregister: true, - // consul server 主机 - host: '192.168.0.10', - // consul server 端口 - port: 8500, - // 调用服务的策略(默认选取 random 具有随机性) - strategy: 'random', }, - service: { - address: '127.0.0.1', - port: 7001, - tags: ['tag1', 'tag2'], - // others consul service definition - } -} +}; ``` +Notes:`options` and `register` are consisitent with [Common Method Call Options](https://github.com/silas/node-consul#common-method-call-options) and [consul agent service register options](https://github.com/silas/node-consul#consulagentserviceregisteroptions) + + + ### 3. lookup the service instance to call. +- Get ConsulService with two ways + +```typescript +import { ConsulService } from '@midwayjs/consul'; +import { App, IMidwayApplication } from '@midwayjs/core'; + +export class Home { + //1. with Inject + @Inject() + consulSrv: ConsulService; + @App() + app: IMidwayApplication; + + //2. with Code; + async home() { + const consulSrv = await app + .getApplicationContext() + .getAsync(ConsulService); + } +} ``` -// 1. 注入的方式 -@Inject('consul:balancerService') -balancerService: IConsulBalancer; -// 2. 编码的方式 -const balancerService = await app.getApplicationContext().getAsync('consul:balancerService'); - -// 查询的 service 数据是 consul 返回的原生数据,因为组件并不知道应用层使用了 consul 的哪些元数据信息 -// 注意下 select 在没有服务实例时会抛出 Error - -// 1. 查询通过健康检查的服务 -const service = await balancerService.getBalancer().select('the-service-name'); -// 2. 可能取到不健康的服务 -const service = await balancerService.getBalancer().select('the-service-name', false); + +- Find service information by name,and You can get the same structure + as [consul's api response](https://developer.hashicorp.com/consul/api-docs/catalog#sample-response-3) + +```typescript +// You can do whatever you want with serviceInfo,like service calls, etc +// By the way,select will only get services that are in a healthy(status is passing) state,If there are multiple instances, select one at random +import console = require("console"); + +try { + const service = await consulSrv.select('SERVICE_NAME'); + console.log(service) +} catch (e) { + // if an exception occurs, you can get an instance of MidwayConsulError,so you can do some magic.. +} ``` +the output [structure](https://developer.hashicorp.com/consul/api-docs/catalog#sample-response-3) is : +```json +[ + { + "ID": "40e4a748-2192-161a-0510-9bf59fe950b5", + "Node": "t2.320", + "Address": "192.168.10.10", + "Datacenter": "dc1", + "TaggedAddresses": { + "lan": "192.168.10.10", + "wan": "10.0.10.10" + }, + "NodeMeta": { + "somekey": "somevalue" + }, + "CreateIndex": 51, + "ModifyIndex": 51, + "ServiceAddress": "172.17.0.3", + "ServiceEnableTagOverride": false, + "ServiceID": "32a2a47f7992:nodea:5000", + "ServiceName": "web", + "ServicePort": 5000, + "ServiceMeta": { + "web_meta_value": "baz" + }, + "ServiceTaggedAddresses": { + "lan": { + "address": "172.17.0.3", + "port": 5000 + }, + "wan": { + "address": "198.18.0.1", + "port": 512 + } + }, + "ServiceTags": ["prod"], + "ServiceProxy": { + "DestinationServiceName": "", + "DestinationServiceID": "", + "LocalServiceAddress": "", + "LocalServicePort": 0, + "Config": null, + "Upstreams": null + }, + "ServiceConnect": { + "Native": false, + "Proxy": null + }, + "Namespace": "default" + } +] + -### 4. inject the origin consul object. ``` -import * as Consul from 'consul'; -// 使用 consul 官方包装的 API 接口 -@Inject('consul:consul') -consul: Consul.Consul; +### 4. Gets the original object of Consul,You can use the original object do you need(likes:KVStore,Events,etc...) + +```typescript +import { ConsulService } from '@midwayjs/consul'; + +export class Home { + @Inject() + consulSrv: ConsulService; + + async home() { + const consulObj = this.consulSrv.consul; + } +} ``` diff --git a/site/docs/extensions/consul.md b/site/docs/extensions/consul.md index 8f3a6c18fa66..1fae37d1f41c 100644 --- a/site/docs/extensions/consul.md +++ b/site/docs/extensions/consul.md @@ -2,29 +2,25 @@ consul 用于微服务下的服务治理,主要特点有:服务发现、服务配置、健康检查、键值存储、安全服务通信、多数据中心等。 -本文介绍了如何用 consul 作为 midway 的服务注册发现中心,以及如何使用 consul来做软负载的功能。 +本文介绍了如何用 consul 作为 midway 的服务注册发现中心。 相关信息: -| 描述 | | -| ----------------- | ---- | -| 可用于标准项目 | ✅ | -| 可用于 Serverless | ❌ | -| 可用于一体化 | ✅ | -| 包含独立主框架 | ❌ | -| 包含独立日志 | ❌ | - - - -感谢 [boostbob](https://github.com/boostbob) 提供的组件。 +| 描述 | | +| ----------------- | --- | +| 可用于标准项目 | ✅ | +| 可用于 Serverless | ❌ | +| 可用于一体化 | ✅ | +| 包含独立主框架 | ❌ | +| 包含独立日志 | ❌ | +感谢 [boostbob](https://github.com/boostbob),[flyingCrp](https://github.com/flyingCrp) 完善和维护组件。 效果如下图: ![image.png](https://img.alicdn.com/imgextra/i3/O1CN01e5cFZx1I0draeZynr_!!6000000000831-2-tps-1500-471.png) ![image.png](https://img.alicdn.com/imgextra/i2/O1CN01iLYF8r1HQ0B3b47Fh_!!6000000000751-2-tps-1500-895.png) - ## 安装组件 首先安装 consul 组件和类型: @@ -39,18 +35,14 @@ $ npm i @types/consul --save-dev ```json { "dependencies": { - "@midwayjs/consul": "^3.0.0", - // ... + "@midwayjs/consul": "^3.0.0" }, "devDependencies": { - "@types/consul": "^0.40.0", - // ... + "@types/consul": "^0.40.0" } } ``` - - ## 目前支持的能力 - 注册能力(可选) @@ -58,26 +50,22 @@ $ npm i @types/consul --save-dev - 服务选择(随机) - 暴露原始的 consul 对象 - - ## 启用组件 ```typescript -import * as consul from '@midwayjs/consul' +import * as consul from '@midwayjs/consul'; @Configuration({ imports: [ // .. - consul + consul, ], - importConfigs: [join(__dirname, 'config')] + importConfigs: [join(__dirname, 'config')], }) export class MainConfiguration {} ``` - - -## 配置 +## 配置文件 配置 `config.default.ts` 文件: @@ -86,32 +74,37 @@ export class MainConfiguration {} export default { // ... consul: { - provider: { - // 注册本服务 - register: true, - // 应用正常下线反注册 - deregister: true, - // consul server 服务地址 - host: '192.168.0.10', - // consul server 服务端口 - port: '8500', - // 调用服务的策略(默认选取 random 具有随机性) - strategy: 'random', - }, - service: { - // 此处是当前这个 midway 应用的地址 + // {可选}, 默认是 undefined + // 如果需要将应用注册到consul,则需要配置该选项,如果仅作为客户端使用,则不需要配置 + // 完整可用配置请查看 https://github.com/silas/node-consul#consulagentserviceregisteroptions + register: { address: '127.0.0.1', - // 当前 midway 应用的端口 port: 7001, - // 做泳道隔离等使用 - tags: ['tag1', 'tag2'], - name: 'my-midway-project' - // others consul service definition - } + //服务名称,默认使用package.json中的name + name: 'SERVICE_NAME', + //服务ID,默认为 `name:address:port` + id: 'SERVICE_ID', + }, + //{必填},consul服务端配置,可用参数:https://github.com/silas/node-consul#consuloptions + options: { + host: '127.0.0.1', + port: '8500', + defaults: { + token: '566e93b6-0740-54ff-3a3a-ecb3f5479859', + }, + }, + //{可选},默认为true,会在应用停止时自动反注册 + deregister: true, }, -} +}; ``` +其中: + +> `options` 与 [node-consul-实例初始化](https://github.com/silas/node-consul#common-method-call-options) 保持一致; +> +> `register` 与 [node-consul-服务注册](https://github.com/silas/node-consul#consulagentserviceregisteroptions) 保持一致; + 打开我们 consul server 的 ui 地址,效果如下: ![image.png](https://img.alicdn.com/imgextra/i1/O1CN01QI7A1d1dU3ECG8QxQ_!!6000000003738-2-tps-1500-471.png) @@ -124,168 +117,147 @@ export default { 我们可以看到我们项目的状态就变为红色。 -我们演示多台的情况,如下表现:(1台在线+1台不在线) +我们演示多台的情况,如下表现:(1 台在线+1 台不在线) ![image.png](https://img.alicdn.com/imgextra/i1/O1CN01kfmul91eSxu5EiJE3_!!6000000003871-2-tps-1500-405.png) ![image.png](https://img.alicdn.com/imgextra/i4/O1CN01PZrdpp21Sir5n3y9I_!!6000000006984-2-tps-1500-360.png) - - ## 作为客户端 例如我们作为客户端 A,需要调用服务 B 的接口,然后我们首先是查出 B 健康的服务,然后进行 http 请求。 - 此处为了方便理解,我们模拟查询刚刚注册的成功的服务: + ```typescript import { Controller, Get, Inject, Provide } from '@midwayjs/core'; -import { BalancerService } from '@midwayjs/consul' +import { ConsulService } from '@midwayjs/consul'; @Provide() @Controller('/') export class HomeController { - @Inject() - balancerService: BalancerService; + consulSrv: ConsulService; @Get('/') async home() { - const service = await this.balancerService.getServiceBalancer().select('my-midway-project'); - - // output - console.log(service) - - // ... + const service = await this.consulSrv.select('my-midway-project'); + console.log(service); } } - ``` -输出的 service 的内容为: -```typescript + +输出的 [service 结构](https://developer.hashicorp.com/consul/api-docs/catalog#sample-response-3): + +```json { - ID: 'c434e36b-1b62-c4e1-c4ec-76c5d3742ff8', - Node: '1b2d5b8771cb', - Address: '127.0.0.1', - Datacenter: 'dc1', - TaggedAddresses: { - lan: '127.0.0.1', - lan_ipv4: '127.0.0.1', - wan: '127.0.0.1', - wan_ipv4: '127.0.0.1' + "ID": "40e4a748-2192-161a-0510-9bf59fe950b5", + "Node": "t2.320", + "Address": "192.168.10.10", + "Datacenter": "dc1", + "TaggedAddresses": { + "lan": "192.168.10.10", + "wan": "10.0.10.10" }, - NodeMeta: { 'consul-network-segment': '' }, - ServiceKind: '', - ServiceID: 'my-midway-project:xxx:7001', - ServiceName: 'my-midway-project', - ServiceTags: [ 'tag1', 'tag2' ], - ServiceAddress: 'xxxxx', - ServiceTaggedAddresses: { - lan_ipv4: { Address: 'xxxxx', Port: 7001 }, - wan_ipv4: { Address: 'xxxxxx', Port: 7001 } + "NodeMeta": { + "somekey": "somevalue" }, - ServiceWeights: { Passing: 1, Warning: 1 }, - ServiceMeta: {}, - ServicePort: 7001, - ServiceEnableTagOverride: false, - ServiceProxy: { MeshGateway: {}, Expose: {} }, - ServiceConnect: {}, - CreateIndex: 14, - ModifyIndex: 14 -} -``` -此时,我们只要通过 Address 和 ServicePort 去连接服务 B,比如做 http 请求。 - - -如果需要查询不健康的,则 `select` 方法的第二个参数传入 false 值: -```typescript -import { Controller, Get, Inject, Provide } from '@midwayjs/core'; -import { BalancerService } from '@midwayjs/consul' - -@Provide() -@Controller('/') -export class HomeController { - - @Inject() - balancerService: BalancerService; - - @Get('/') - async home() { - - const service = await this.balancerService - .getServiceBalancer() - .select('my-midway-project', false); - - console.log(service); - - // ... - } + "CreateIndex": 51, + "ModifyIndex": 51, + "ServiceAddress": "172.17.0.3", + "ServiceEnableTagOverride": false, + "ServiceID": "32a2a47f7992:nodea:5000", + "ServiceName": "web", + "ServicePort": 5000, + "ServiceMeta": { + "web_meta_value": "baz" + }, + "ServiceTaggedAddresses": { + "lan": { + "address": "172.17.0.3", + "port": 5000 + }, + "wan": { + "address": "198.18.0.1", + "port": 512 + } + }, + "ServiceTags": ["prod"], + "ServiceProxy": { + "DestinationServiceName": "", + "DestinationServiceID": "", + "LocalServiceAddress": "", + "LocalServicePort": 0, + "Config": null, + "Upstreams": null + }, + "ServiceConnect": { + "Native": false, + "Proxy": null + }, + "Namespace": "default" } - ``` +此时,我们只要通过 `ServiceAddress` 和 `ServicePort` 去连接服务 B,如果`ServiceAddress`为空则应该使用`Address`,比如做 http 请求。 - -## 配置中心 - +## 配置中心(KV Store) 同时 consul 也能作为一个服务配置的地方,如下代码: + ```typescript import { Controller, Get, Inject } from '@midwayjs/core'; -import * as Consul from 'consul'; +import { ConsulService } from '@midwayjs/consul'; @Controller('/') export class HomeController { - - @Inject('consul:consul') - consul: Consul.Consul; + @Inject() + consulSrv: ConsulService; @Get('/') async home() { - await this.consul.kv.set(`name`, `juhai`) - // let res = await this.consul.kv.get(`name`); - // console.log(res); + await this.consulSrv.consul.kv.set(`name`, `Hello Midwayjs!`); + // let res = await this.consulSrv.kv.get(`name`); + // console.log(res.Value); return 'Hello Midwayjs!'; } } - ``` -我们调用 `kv.set` 方法,我们可以设置对应的配置,通过 `kv.get` 方法可以拿到对应的配置。 +我们调用 `kv.set` 方法,我们可以设置对应的配置,通过 `kv.get` 方法可以拿到对应的配置。 注意:在代码中,有同学出现,在每次请求中去 get 对应的配置,这时你的 QPS 多少对 Consul server 的压力。 +所以在 QPS 比较大的情况,可以如下处理: -所以在QPS比较大的情况,可以如下处理: ```typescript import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import * as Consul from 'consul'; +import { ConsulService } from '@midwayjs/consul'; @Provide() @Scope(ScopeEnum.Singleton) export class ConfigService { - - @Inject('consul:consul') - consul: Consul.Consul; + @Inject() + consulSrv: ConsulService; config: any; @Init() async init() { - setInterval(()=>{ - this.consul.kv.get(`name`).then(res=>{ + setInterval(() => { + this.consulSrv.consul.kv.get(`name`).then((res) => { this.config = res; - }) + }); }, 5000); this.config = await this.consul.kv.get(`name`); } - async getConfig(){ + async getConfig() { return this.config; } } - ``` + 上面的代码,相当于定时去获取对应的配置,当每个请求进来的时候,获取 Scope 为 ScopeEnum.Singleton 服务的 `getConfig` 方法,这样每 5s 一次获取请求,就减少了对服务的压力。 Consul 界面上如下图: @@ -294,38 +266,29 @@ Consul 界面上如下图: ![image.png](https://img.alicdn.com/imgextra/i2/O1CN014O2GyH1sMvIhmlbs4_!!6000000005753-2-tps-1500-667.png) - 一共提供如下几种方法: -- [get](https://www.npmjs.com/package/consul#kv-get),获取对应key的value -- [keys](https://www.npmjs.com/package/consul#kv-keys),查询某个prefix的key的列表 -- [set](https://www.npmjs.com/package/consul#kv-set),设置对应的key的值 -- [del](https://www.npmjs.com/package/consul#kv-del),删除对应的key - - - -## 其他说明 - - -这样的好处,就是 A->B,B 也可以进行扩展,并且可以通过 tags 做泳道隔离。例如做单元隔离等。并且可以通过 ServiceWeights 做对应的权重控制。 - - -Consul 还能做 Key/Value 的配置中心的作用,这个后续我们考虑支持。 - +- [get](https://www.npmjs.com/package/consul#kv-get),获取对应 key 的 value +- [keys](https://www.npmjs.com/package/consul#kv-keys),查询某个 prefix 的 key 的列表 +- [set](https://www.npmjs.com/package/consul#kv-set),设置对应的 key 的值 +- [del](https://www.npmjs.com/package/consul#kv-del),删除对应的 key ## 搭建 Consul 测试服务 - 下面描述了单机版本的 consul 搭建流程。 + ```bash docker run -itd -P consul ``` + 然后执行 `docker ps` + ```bash ➜ my_consul_app docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1b2d5b8771cb consul "docker-entrypoint.s…" 4 seconds ago Up 2 seconds 0.0.0.0:32782->8300/tcp, 0.0.0.0:32776->8301/udp, 0.0.0.0:32781->8301/tcp, 0.0.0.0:32775->8302/udp, 0.0.0.0:32780->8302/tcp, 0.0.0.0:32779->8500/tcp, 0.0.0.0:32774->8600/udp, 0.0.0.0:32778->8600/tcp cocky_wing ``` + 然后我们打开 8500 所对应的端口:(上图比如我的对应端口是 32779) [http://127.0.0.1:32779/ui/](http://127.0.0.1:32779/ui/dc1/kv) @@ -334,34 +297,32 @@ CONTAINER ID IMAGE COMMAND CREATED ![](https://img.alicdn.com/imgextra/i2/O1CN014O2GyH1sMvIhmlbs4_!!6000000005753-2-tps-1500-667.png) -然后我们的 `config.default.ts` 中的port就是 32779 端口。 +然后我们的 `config.default.ts` 中的 port 就是 32779 端口。 +## 下线服务 +如果想要手动将 consul 界面上不需要的服务给下线掉,可以通过下面的方法: -## 下线服务 -如果想要手动将consul界面上不需要的服务给下线掉,可以通过下面的方法: ```typescript import { Controller, Get, Inject, Provide } from '@midwayjs/core'; -import * as Consul from 'consul' +import { ConsulService } from '@midwayjs/consul'; @Provide() @Controller('/') export class HomeController { + @Inject() + consulSrv: ConsulService; - @Inject('consul:consul') - consul: Consul.Consul; - - @Get("/222") - async home2(){ - let res = await this.consul.agent.service.deregister(`my-midway-project:30.10.72.195:7002`); + @Get('/222') + async home2() { + let res = await this.consulSrv.consul.agent.service.deregister(`my-midway-project:30.10.72.195:7002`); console.log(res); // ... } - } - ``` + `deregister` 方法,对应 consul 界面上的名字。 ![image.png](https://img.alicdn.com/imgextra/i2/O1CN01d5QMUJ1DULTKPSJsr_!!6000000000219-2-tps-1500-465.png) From e5eac9cd6f113072c90e75f77b7946000cfeca61 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Mon, 14 Aug 2023 23:23:58 +0800 Subject: [PATCH 2/2] chore: use consul beta version include typigns --- packages/consul/package.json | 3 +-- packages/consul/src/consul.service.ts | 6 +++--- packages/consul/src/interface.ts | 7 ++++--- packages/consul/tsconfig.json | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/consul/package.json b/packages/consul/package.json index 6069f31f59d8..ab5c7025c112 100644 --- a/packages/consul/package.json +++ b/packages/consul/package.json @@ -13,12 +13,11 @@ "@midwayjs/core": "^3.11.15", "@midwayjs/koa": "^3.11.15", "@midwayjs/mock": "^3.11.15", - "@types/consul": "0.40.0", "@types/sinon": "10.0.16", "nock": "13.3.2" }, "dependencies": { - "consul": "1.2.0" + "consul": "2.0.0-next.1" }, "keywords": [ "consul" diff --git a/packages/consul/src/consul.service.ts b/packages/consul/src/consul.service.ts index 11e412737536..67d3d090319c 100644 --- a/packages/consul/src/consul.service.ts +++ b/packages/consul/src/consul.service.ts @@ -9,7 +9,7 @@ import { Provide, Singleton, } from '@midwayjs/core'; -import * as Consul from 'consul'; +import Consul from 'consul'; import { IConsulOptions, IServiceHealth, IServiceNode } from './interface'; export class MidwayConsulError extends MidwayError { @@ -30,9 +30,9 @@ export class ConsulService { serviceId: string; - private instance: Consul.Consul; + private instance: Consul; - get consul(): Consul.Consul { + get consul(): Consul { return this.instance; } diff --git a/packages/consul/src/interface.ts b/packages/consul/src/interface.ts index fde3227b6da6..fb2eff6e5d66 100644 --- a/packages/consul/src/interface.ts +++ b/packages/consul/src/interface.ts @@ -1,5 +1,6 @@ -import * as Consul from 'consul'; -import { ConsulOptions } from 'consul'; +import Consul from 'consul'; + +type ConsulOptions = ConstructorParameters[0]; /** * consul configuration of midwayjs @@ -20,7 +21,7 @@ export interface IConsulOptions { * *@see [consul.agent.service.register.options]{@link https://github.com/silas/node-consul#consulagentserviceregisteroptions} */ - register: Consul.Agent.Service.RegisterOptions; + register: typeof Consul.Agent.Service; } /** diff --git a/packages/consul/tsconfig.json b/packages/consul/tsconfig.json index b3efee2a106b..7d8bb6abea67 100644 --- a/packages/consul/tsconfig.json +++ b/packages/consul/tsconfig.json @@ -1,9 +1,9 @@ { "extends": "../../tsconfig.json", - "compileOnSave": true, "compilerOptions": { "rootDir": "src", - "outDir": "dist" + "outDir": "dist", + "esModuleInterop": true }, "include": [ "./src/**/*.ts"