Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(consul): consul's implementation was refactored (#3109) #3163

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/consul/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 3 additions & 15 deletions packages/consul/src/config/config.default.ts
Original file line number Diff line number Diff line change
@@ -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,
};
92 changes: 2 additions & 90 deletions packages/consul/src/configuration.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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<void> {
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<void> {
await this.registerConsul(container, app);
}

async onStop(): Promise<void> {
if (
this.consulProviderConfig.register &&
this.consulProviderConfig.deregister
) {
const { id } = this.consulRegisterConfig;
await this.consulProvider.deregisterService({ id });
}
}
}
export class ConsulConfiguration implements ILifeCycle {}
158 changes: 158 additions & 0 deletions packages/consul/src/consul.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {
Autoload,
Config,
Destroy,
Init,
Inject,
MidwayError,
MidwayInformationService,
Provide,
Singleton,
} from '@midwayjs/core';
import 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;

get consul(): Consul {
return this.instance;
}

private selectRandomService(arr: Array<any>) {
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<Array<IServiceNode>> {
const services: Array<IServiceNode> =
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<Consul.Catalog.Service.NodesOptions, 'service'>
) {
const checkOpt = options || {};
let checkedArr: Array<IServiceHealth>;
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<IServiceHealth> = 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);
}
}
}
10 changes: 4 additions & 6 deletions packages/consul/src/controller/consul.ts
Original file line number Diff line number Diff line change
@@ -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<any> {
return {
status: 'success',
};
@Get('/health')
async healthCheck() {
return 'success';
}
}
2 changes: 1 addition & 1 deletion packages/consul/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading