☰
Midway 接入 Consul:服务注册、负载均衡与健康检查实战指南
2026/9/29 21:39:12 网站建设 项目流程
  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

导读

本文基于 Midway 官方子包@midwayjs/consul(本仓库位于 packages/consul),系统讲解如何在 Midway 应用中集成 Consul,实现服务注册(register)、应用下线反注册(deregister)、基于健康检查实例的服务发现与负载均衡,以及直接注入原生 Consul 客户端进行底层操作。读完本文,你将掌握从安装依赖、编写配置、注册服务,到通过consul:balancerService调用远端服务实例的完整链路,并了解服务发现模块(ConsulServiceDiscovery)背后的源码实现与健康检查机制,可直接在基于 Koa / Express / Egg 的 Midway 微服务项目中落地使用。

一、Consul 组件能做什么

@midwayjs/consul是 Midway 生态中面向微服务注册与发现的标准组件,能力清单如下(与 packages/consul/usage.md 中 Support 列表一致):

  • register(可选):应用启动时向 Consul Server 注册当前服务实例;
  • deregister on the shutdown(可选):应用正常关闭时自动反注册,避免留下“僵尸实例”;
  • service balancer(默认 random):组件内置负载均衡器,在多个健康实例中挑选一个进行调用,默认采用随机策略;
  • expose the origin consul object:暴露原始consul客户端对象,方便直接使用官方库的底层 API(如 KV 存储、健康检查查询等)。

组件基于consulnpm 包(dependencies中锁定consul@2.0.1)封装,运行要求 Node.js >= 20,详见 packages/consul/package.json。

二、安装与引入组件

2.1 安装依赖

在 Midway 项目中安装运行时依赖与 TypeScript 类型声明:

npm i @midwayjs/consul -S npm i @types/consul -D
  • -S:安装到dependencies,运行时需要;
  • -D:安装@types/consul到devDependencies,仅 TypeScript 编译期使用。

2.2 引入组件

在入口configuration.ts中,通过imports引入consul模块,并通过importConfigs声明配置目录:

import * as consul from '@midwayjs/consul' @Configuration({ imports: [ consul ], importConfigs: [join(__dirname, 'config')] }) export class ContainerConfiguration {}

组件内部在ConsulConfiguration(见 packages/consul/src/configuration.ts)中完成了生命周期管理:

  • onReady:应用就绪时通过容器获取ConsulServiceFactory,提前完成客户端初始化;
  • onStop:应用停止时调用factory.stop(),触发destroyClient销毁连接(packages/consul/src/manager.ts 中会打印[midway:consul] destroy %s日志)。

因此你只需引入组件并写好配置,连接的生命周期由框架自动托管。

三、配置 Consul Server 与服务定义

在配置目录(即importConfigs指向的config文件夹)下的config.default.ts或config.default.js中写入:

consul: { provider: { // 注册本服务 register: true, // 应用正常下线反注册 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 } }

各字段含义与取值说明:

配置段字段说明默认值/备注
providerregister是否向 Consul 注册当前服务布尔值,可选;为true时启动即注册
providerderegister应用正常关闭时是否反注册布尔值,可选;为true时onStop阶段调用agent.service.deregister
providerhostConsul Server 地址例如192.168.0.10
providerportConsul Server 端口默认8500(Consul HTTP API 标准端口)
providerstrategy实例选取策略默认random,具有随机性
serviceaddress本服务对外暴露的 IP/主机例如127.0.0.1
serviceport本服务对外暴露的端口例如7001
servicetags注册时附加的标签数组例如['tag1', 'tag2'],可用于按标签筛选实例
service其余字段其他 Consul 服务注册定义直接透传给官方agent.service.register

从源码看,客户端工厂ConsulServiceFactory.createClient(packages/consul/src/manager.ts)会把consul配置中的host、port等直接作为参数构造原生new Consul(config)客户端,并在初始化时输出日志[midway:consul] init %s at %s:%s,方便排查连接目标。

3.1 关于服务发现(serviceDiscovery)的进阶配置

除了上述provider/service两段,组件默认配置中还预置了服务发现相关参数(见 packages/consul/src/configuration.ts):

consul: { serviceDiscovery: { loadBalancer: 'roundRobin', healthCheckType: 'self', }, }
  • loadBalancer:服务发现客户端的负载均衡器类型,组件默认值为roundRobin;
  • healthCheckType:健康检查方式,默认self(自管理健康检查)。

ConsulServiceDiscovery(packages/consul/src/extension/serviceDiscovery.ts)在初始化时通过MidwayConfigService.getConfiguration('consul.serviceDiscovery', {})读取该段配置,并据此创建对应的服务发现客户端。

四、查询服务实例并调用(负载均衡)

当需要调用其他注册到 Consul 的服务时,通过consul:balancerService获取负载均衡器。支持两种获取方式:

// 1. 注入的方式 @Inject('consul:balancerService') balancerService: IConsulBalancer; // 2. 编码的方式 const balancerService = await app.getApplicationContext().getAsync<IConsulBalancer>('consul:balancerService');

然后通过getBalancer().select(name)选取一个实例:

// 1. 查询通过健康检查的服务 const service = await balancerService.getBalancer().select('the-service-name'); // 2. 可能取到不健康的服务 const service = await balancerService.getBalancer().select('the-service-name', false);

两点必须注意:

  1. 返回的是 Consul 原生数据:select返回的service是 Consul API 返回的原始健康检查条目结构(含Node、Service、Checks等字段,类型定义见 packages/consul/src/interface.ts 中的ConsulHealthItem),组件并不知道应用层使用 Consul 的哪些元数据信息,因此不做过多的结构转换;
  2. 无实例时会抛 Error:select在“没有任何可用服务实例”的场景下会抛出异常,业务代码需要自行捕获或降级处理。

select的第二个参数控制是否只选取健康实例:默认(true/缺省)仅返回通过健康检查的实例;传入false时则可能取到不健康的实例。

4.1 与 Service Discovery 的关系

consul:balancerService的负载均衡能力建立在服务发现之上。ConsulServiceDiscovery对外提供以下核心方法(见 packages/consul/src/extension/serviceDiscovery.ts):

  • createClient():创建服务发现客户端,内部实现register/deregister/online/offline/beforeStop等方法;
  • getInstances(options):获取某个服务当前的健康实例列表(内部强制passing: true,即只返回健康检查通过的实例);
  • getInstance(options):从实例列表中选取单个实例。

其数据链路可以总结为:

  1. ConsulDataListener首次调用client.health.service(options)拉取全量实例作为初始数据(initData);
  2. 随后通过client.watch({ method: client.health.service, options })建立 Consul watch 长轮询监听,实例列表变化时自动setData更新(见onData);
  3. 多个查询请求按hashServiceOptions(基于排序 key 的 DJB2 变体哈希,见 packages/consul/src/utils.ts)做缓存,同一服务复用同一个ConsulDataListener,避免重复建立 watch;
  4. 应用停止时beforeStop统一销毁所有 listener 的 watcher。

从源码结构看,ConsulServiceDiscoverClient.register的流程还包括:注册成功后调用online()把实例状态置为UP(内部走agent.check.pass(checkId),checkId 形如service:${instance.id}),offline()则对应agent.check.fail。测试用例 packages/consul/test/serviceDiscovery.test.ts 中完整演示了注册(携带check: { name, timeout, ttl }的 TTL 健康检查)、getInstances查询、getInstance选取,以及offline()后健康实例列表归零的完整流程。

五、注入原生 Consul 对象

除了组件封装的高层能力,还可以直接注入官方consul客户端,使用其完整 API:

import * as Consul from 'consul'; // 使用 consul 官方包装的 API 接口 @Inject('consul:consul') consul: Consul.Consul;

注入后即可直接调用官方库方法,例如:

// KV 读写 await this.consul.kv.set('my/key', 'my-value'); const res = await this.consul.kv.get('my/key'); // 查询健康服务 const services = await this.consul.health.service({ service: 'the-service-name' });

底层实现上,ConsulService(packages/consul/src/manager.ts)是一个@Singleton(),初始化时从ConsulServiceFactory中取出默认客户端(getDefaultClientName()未定义时取'default'),并通过delegateTargetAllPrototypeMethod(ConsulService, Consul)把原生 Consul 类的原型方法全部委托到该实例上,因此注入后具备与原生客户端完全一致的方法集。

5.1 多客户端与自定义客户端

ConsulServiceFactory继承自 Midway 的ServiceFactory,支持按consul.client.xxx的形式配置多个客户端(测试用例 packages/consul/test/index.test.ts 中展示了consul.client.host/port的配置写法)。此外createClient支持customClientClass配置项——若传入自定义类,则使用new customClientClass(otherConfig)构造客户端(packages/consul/test/customClient.test.ts 有对应用例),适合替换或包装客户端实现的场景。

六、健康检查机制(源码级解读)

当service定义中携带check字段且autoHealthCheck开启时,ConsulServiceDiscoverClient.register会根据检查类型自动启动对应的健康检查器(实现位于 packages/consul/src/extension/helper.ts):

检查类型触发条件实现类行为
TTL 心跳check.ttl存在TTLHeartbeat按calculateTTL(check.ttl)计算间隔(最小 1000ms,支持"30s"、"1m"等 ms 库格式),周期性调用agent.check.pass(checkId)上报心跳,失败时输出错误日志
HTTP 探活check.http存在HTTPHealthCheck解析 URL 的端口与路径,若当前应用是 Egg/Koa/Express 应用则把该路径注册进框架路由返回success;否则自起一个 HTTP 服务在对应端口路径返回ok
TCP 探活check.tcp存在TCPHealthCheck自起一个 TCP 服务监听对应端口/主机,连接成功即关闭,用于验证端口可达

应用关闭时,beforeStop会依次停止 TTL 心跳与 HTTP/TCP 健康检查服务,并完成deregister()反注册,确保资源释放干净。

七、完整的最小可运行示例

综合以上内容,一个完整的最小接入方案如下:

1. 安装:

npm i @midwayjs/consul -S npm i @types/consul -D

2. 入口 configuration.ts:

import { Configuration } from '@midwayjs/core'; import { join } from 'path'; import * as consul from '@midwayjs/consul'; @Configuration({ imports: [consul], importConfigs: [join(__dirname, 'config')], }) export class ContainerConfiguration {}

3. 配置 config/config.default.ts:

export default { consul: { provider: { register: true, deregister: true, host: '192.168.0.10', port: 8500, strategy: 'random', }, service: { address: '127.0.0.1', port: 7001, tags: ['tag1', 'tag2'], check: { name: 'TTL Health Check', timeout: '30s', ttl: '10s', }, }, }, };

4. 调用远端服务:

import { Inject, Provide } from '@midwayjs/core'; import { IConsulBalancer } from '@midwayjs/consul'; @Provide() export class RemoteCallService { @Inject('consul:balancerService') balancerService: IConsulBalancer; async call() { // 只选健康实例 const healthy = await this.balancerService.getBalancer().select('user-service'); // 或允许不健康实例 const any = await this.balancerService.getBalancer().select('user-service', false); return { healthy, any }; } }

八、常见问题与注意事项

  • select 抛错:没有可用的服务实例时select会抛出Error,请务必 try/catch 或结合降级逻辑(如直接返回默认地址)使用;
  • 注册与反注册的开关:register/deregister均为可选开关,生产环境建议都开启,避免服务下线后实例仍在 Consul 中残留;
  • 策略选择:provider.strategy默认random,若需均匀分发可关注serviceDiscovery.loadBalancer的roundRobin配置(组件默认即roundRobin);
  • 返回数据为原生结构:select/getInstances返回的是 Consul 健康检查原始数据结构(Node/Service/Checks),字段名遵循 Consul API 的大写风格,需按 ConsulHealthItem 类型定义取用;
  • 多环境配置:Midway 的importConfigs机制支持config.default/config.prod/config.test等按环境覆盖 Consul 地址与开关,便于本地与线上隔离。

九、更多源码入口

  • 组件生命周期与默认配置:packages/consul/src/configuration.ts
  • 客户端工厂、原生客户端注入与委托:packages/consul/src/manager.ts
  • 服务发现与数据监听(watch、缓存、注册/反注册):packages/consul/src/extension/serviceDiscovery.ts
  • TTL 心跳 / HTTP / TCP 健康检查实现:packages/consul/src/extension/helper.ts
  • 类型定义(ConsulHealthItem、GetHealthServiceOptions、ConsulServiceDiscoveryOptions):packages/consul/src/interface.ts
  • 官方用法文档:packages/consul/usage.md
  • 功能测试用例:packages/consul/test/index.test.ts 与 packages/consul/test/serviceDiscovery.test.ts
  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询