为什么需要微服务的动态注册与动态发现?
后端架构在业务复杂时,好的解决方案就是用微服务做解耦。每个服务只开发相对应业务功能,如果服务流量大,还可以多部署几台服务器。
那服务多了以后,就涉及到了都有啥服务呀?服务下的哪个实例是好的呀?等等问题。
动态注册比较好理解,当我一台服务起来以后,自动向服务中心发送一条通知:XX 服务上线了一个实例!
那为啥要动态发现?我理解的有两方面:
服务有新节点上线、老节点崩溃等行为,网关需要根据节点状态将流量转发到正常的节点上去。
如果每增加一种服务,网关就开发对应的转发规则,那不就开发两遍了吗?
别管加了什么功能,你约定一个前缀,我直接就发给你,多好!
什么是 Nacos?
Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
阿里巴巴开发,因为官方提供 Java SDK,所以国内 Java 的微服务大部分都是用的 Nacos。
其实只要是微服务架构都可以使用,像:go、nodejs、python 等等。
说到底还是 API 的调用,哪怕你不是微服务架构,也可以通过调用接口来用用配置中心或开发个服务状态监控页啥的。
如何实现微服务的动态注册与动态发现?
这里我们简单做个区分:
- nacos
- gateway
- 端口:
3000
- Nest 网关服务,提供 HTTP 接口访问并转发到对应的微服务
- ms_aaa
- 端口:
4000
- Nest 微服务,我们约定路径以
/aaa
开头的都会被转发到这个微服务
- ms_bbb
- 端口:
5000
- Nest 微服务,我们约定路径以
/bbb
开头的都会被转发到这个微服务
Nacos 安装
Nacos 支持二进制安装、Docker 安装、Kubernetes 安装,根据自己喜好来,安装还是比较简单的。
因为有 NAS,所以我这里直接在 Container Manager 里启动 Docker 镜像。
本地测试环境变量 MODE
必须改为 standalone
表示单机模式。而默认的 cluster
表示集群,需要配置一些额外的变量,是正式环境下,多容器的建议方式。
本地测试不必填写数据库相关环境变量,因为内置了 Derby
,够咱们测试使用了。
安装完启动后访问 http://ip:8848/nacos/
显示如下:
Nest.js 项目创建
通过命令创建 3 个项目,项目代码可以看 example_nest_nacos 仓库。
1 2 3 4 5 6 7 8
| nest new gateway
nest new ms_aaa
nest new ms_bbb
|
微服务改造
在微服务 ms_aaa
和 ms_bbb
同样执行以下操作。
- 安装依赖包
1
| pnpm install @nestjs/microservices nacos-config nacos-naming
|
- 添加
src/nacos.ts
文件,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import { NacosConfigClient } from 'nacos-config'; import { NacosNamingClient } from 'nacos-naming';
const serverAddr = '192.168.28.28:8848';
export const startNacos = async (props) => { const { group = 'DEFAULT_GROUP', namespace = 'public', ip, port, serverName, } = props;
/** * 服务中心 */ // 创建客户端 const naming = new NacosNamingClient({ logger: console, namespace, serverList: serverAddr, });
// 初始化 await naming.ready();
// 注册服务 await naming.registerInstance( serverName, { enabled: true, healthy: true, instanceId: `${ip}:${port}`, ip, port, }, group, );
naming.subscribe(serverName, (value) => { console.log('naming subscribe :>> ', value); });
const config = new NacosConfigClient({ namespace, serverAddr, });
// 订阅通知 config.subscribe( { dataId: serverName, group, }, (value) => { console.log('config subscribe :>> ', value); }, ); };
|
- 将
src/main.ts
改为如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { NestFactory } from '@nestjs/core'; import { Transport } from '@nestjs/microservices'; import { AppModule } from './app.module'; import { startNacos } from './nacos';
async function bootstrap() { const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.TCP, options: { port: 4000, }, });
startNacos({ ip: 'localhost', port: 4000, serverName: 'ms_aaa', });
await app.listen(); } bootstrap();
|
- 在
src/app.controller.ts
中添加方法以供测试
1 2 3 4 5 6 7 8 9 10 11
| import { MessagePattern } from '@nestjs/microservices';
@Controller() export class AppController {
@MessagePattern('hello') hello(): string { return 'hello from ms_aaa'; } }
|
执行 pnpm run start:dev
之后,应该能在 Nacos 里看到两个服务了。
同时,它们还能监听配置 ms_aaa
和 ms_bbb
的变化。
网关服务改造
- 安装依赖包
1
| pnpm install @nestjs/microservices nacos-config nacos-naming
|
- 获取服务列表
没有某个方法可以直接获取所有服务和对应实例列表,所以只能先 查询服务列表,然后再根据查询结果 serviceName
调用 getAllInstances
方法、selectInstances
方法或 subscribe
方法。
- 将
src/app.controller.ts
改为如下代码:
我只是简单写了下 Get 请求做测试,动态连接微服务也直接写在了函数中,这些都需要自行优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import { Controller, Get, Req } from '@nestjs/common'; import { ClientProxyFactory, Transport } from '@nestjs/microservices'; import { NacosNamingClient } from 'nacos-naming'; import { AppService } from './app.service';
@Controller() export class AppController { naming;
serviceList = { count: 0, data: [], map: {}, };
constructor(private readonly appService: AppService) { this.naming = new NacosNamingClient({ logger: console, namespace: 'public', serverList: '192.168.28.28:58848', });
this.naming._serverProxy .getServiceList(1, 20, 'DEFAULT_GROUP') .then((service) => { this.serviceList.count = service.count; this.serviceList.data = service.data;
service.data.forEach((serviceName) => { this.naming.getAllInstances(serviceName).then((instance) => { console.log('getAllInstances :>> ', instance); });
this.naming.selectInstances(serviceName).then((instance) => { console.log('selectInstances :>> ', instance);
this.serviceList.map[serviceName] = instance; });
this.naming.subscribe(serviceName, (hosts) => { console.log('subscribe :>> ', hosts);
this.naming.selectInstances(serviceName).then((instance) => { this.serviceList.map[serviceName] = instance; }); }); }); }); }
@Get('*') get(@Req() req): any { const serviceName = req.url.startsWith('/aaa') ? 'ms_aaa' : 'ms_bbb';
const microservice = ClientProxyFactory.create({ transport: Transport.TCP, options: { host: this.serviceList.map[serviceName][0].ip, port: this.serviceList.map[serviceName][0].port, }, });
return microservice.send('hello', '123123'); } }
|
结尾
至此,访问 /aaa/hello
会返回 hello from ms_aaa
,而 /bbb/hello
会返回 hello from ms_bbb
。
源码查看 example_nest_nacos 仓库,拜~。