想必大家都写过 http 服务,接受 http 请求、做一些处理、返回 http 响应。
这样完成 web 服务器的功能没问题,但随着功能的越来越多,比如现在有一百多个模块了,总不能都放在一个服务里吧,这样管理不方便。
于是就有了拆分的需求,也就有了微服务的概念。
拆分微服务是很自然的事情,但有个问题,微服务和 http 服务之间怎么通信呢?
用 HTTP?
这个是可以,但是 HTTP 是文本协议,传输效率太低了。
所以一般都直接用 TCP 通信。
微服务架构是主流了,各种服务端开发框架都提供了微服务的支持,Nest 自然也不例外。
而且,Nest 对微服务封装的很好,写个微服务是特别简单的事情。
不信我们一起来写一个吧!
首先全局安装 nest 的 cli 工具:
npm i -g @nestjs/cli
然后用 nest 的 cli 快速创建一个 nest 项目:
nest new xxx
选一个依赖管理工具,我这里用的 yarn。
执行 yarn start 就可以看到跑起来的 http 服务了:
浏览器访问下 http://localhost:3000
到这一步,http 服务就启动成功了。
然后我们创建个微服务,同样的方式,用 nest new 创建个项目:
nest new micro-service-calc
这里要创建微服务,需要安装一个包:
yarn add @nestjs/microservices
然后改下 main.ts:
之前创建 http 服务是这样的:
现在要改成这样:
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
port: 8888,
},
},
);
app.listen();
}
bootstrap();
很容易理解,之前是启 http 服务,现在是起微服务了嘛,所以启动方式不一样。
启动的时候指定用 TCP 来传输消息,然后指定 TCP 启动的端口为 8888。
之后在 AppController 里注册下怎么处理 TCP 的消息:
这里用 MessagePattern 的方式来声明处理啥消息:
import { Controller, Get } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class AppController {
constructor() {}
@MessagePattern('sum')
sum(numArr: Array<number>): number {
return numArr.reduce((total, item) => total + item, 0);
}
}
这个 sum 方法就是接受 sum 消息,返回求和的结果的 handler。
然后同样是 yarn start 把这个微服务跑起来:
现在我们有两个服务了:
一个 http 服务,一个 TCP 协议的微服务,然后把两者连起来就可以了。
怎么连起来呢?
我们来改造下 http 服务。
先安装 @nestjs/microservices 依赖:
yarn add @nestjs/microservices
然后在 app.module.ts 里注册 calc 那个微服务:
调用 ClientModule.register 指定名字、传输方式为 TCP、端口为 8888。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'CALC_SERVICE',
transport: Transport.TCP,
options: {
port: 8888,
},
},
]),
],
controllers: [AppController],
providers: [],
})
export class AppModule {}
这样就注册完了。
然后就可以用了,在 Controller 里注入这个微服务的 clientProxy,也就是客户端代理。
import { Controller, Get, Inject, Query } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Observable } from 'rxjs';
@Controller()
export class AppController {
constructor(@Inject('CALC_SERVICE') private calcClient: ClientProxy) {}
@Get()
calc(@Query('num') str): Observable<number> {
const numArr = str.split(',').map((item) => parseInt(item));
return this.calcClient.send('sum', numArr);
}
}
这样就可以接收到 http 请求的时候调用微服务来处理了。
比如上面我们在收到请求的时候,调用代理对象的 send 方法发了一个 TCP 消息给微服务。
这也是为啥叫做 ClientProxy 了,不用你自己发 TCP 消息,你只要调用 send 方法即可。
然后把它重新跑起来:
yarn start
然后,看:
我们 num 传了 1,2,3,这里返回了 6 ,这明显就是 calc 微服务处理的。
这样,我们第一个 Nest 微服务就跑成功了!
是不是挺简单的?
其实微服务还有一种消息传输的类型,这里我们需要响应,所以是 message 的方式,如果不需要响应,那就可以直接声明 event 的方式。
我们再来创建个微服务,用来打印日志。
用 nest new mirco-app-log 创建项目,然后安装 @nestjs/microservices 包,之后像上一个微服务一样改用 createMicroservice 的 api 启动服务。
这个微服务起在 9999 端口。
然后 Controller 改一下:
这里不需要响应,只是处理事件,所以不用 MessagePattern 注册消息了,用 EventPattern。
然后在 main 项目里注册下:
名字叫做 LOG_SERVICE,端口 9999。
然后在 Controller 里注入这个微服务的 clientProxy:
这样我们在这个 http 请求的 handler 里同时用到了两个微服务:
用 calc 微服务来做计算,用 log 微服务来记录日志。
yarn start 重跑一下。
浏览器刷新下:
同样返回了 6,说明 calc 微服务正常。
再去 log 微服务的控制台看看:
log 的微服务打印了日志,说明 log 微服务正常。
至此,Nest 微服务跑成功了!
完整 demo 代码上传了 github:https://github.com/QuarkGluonPlasma/nest-microservice-demo
总结
http 服务大了难免要拆分,现在都是拆成微服务的方式,http 服务负责处理 http 请求,微服务完成不同模块的业务逻辑处理。
微服务和 http 服务之间用 TCP 通信。
用 nest 跑个微服务的步骤如下:
用 nest new 创建一个 main 服务,一个微服务 都要安装 @nestjs/microservices 包,因为用到其中的 api 微服务里用 createMicroservice 启动服务,选择传输方式为 TCP,指定端口 微服务里在 Controller 使用 MessagePattern 或者 EventPattern 注册处理消息的 handler main 服务使用 ClientsModule.register 来注册微服务 main 服务里注入 ClientProxy 对象,调用它的 send 方法给微服务发消息
这就是 Nest 跑微服务的方式。
当然,现在都是本机部署的,你完全可以把微服务放到不同的服务器,甚至可以不同微服务用不同的集群部署。
Nest 里跑微服务以及 http 服务里注册微服务的方式,还是挺简单的,这块封装的确实好。