• NESTJS 微服务


    一、定义

    微服务的定义即为将相同模块或相关业务的操作,封装在一个服务中,达到独立运行、独立部署的效果。目的是为了功能的解耦,并且做到互不影响。

    此时的服务可以采用不用的语言、不一样的架构实现,便于适合不同的开发人员根据自身的技术情况进行灵活选择。

    设计微服务的时候,最主要的是根据业务逻辑、安全、稳定、高效等方面进行服务功能的分离,但在此之外,还要设计服务之间相互通讯的方式。

    最普遍的是通过服务间HTTP接口进行功能的调用,该方式相对来说最易于实现,整个调用流程也较为清晰易懂。但采用HTTP请求进行微服务间通讯也有些缺点,如不必要的请求头信息,导致发送的数据包过大,再比如很难实现任务队列的功能等,所以微服务之间还可以选择RPC、mq、MQTT等方式进行信息流转。

    微服务还有很多注意事项,如服务管理、负载均衡、服务监控等问题。与本篇文章无太大关联,在此不会进行阐述

    二、NESTJS微服务

    nestjs是一种类似angular与spring boot的nodejs后端架构,其架构思想包含DI(依赖注入)、OOP(面向对象编程)、AOP(切面编程)等特点,使得原本较为松散的后端js工程代码能够有较为清晰的管理方式。详情请戳nestjs官网

    nestjs本身除了可设计普通的API服务外,还可以以Microservice的方式设计微服务,在其官方文档中可以看到相关的描述与定义:地址。通过左侧的导航栏可以看到,该框架支持多种介质的服务实现方式,包括redis、MQTT、rabbitmq、gRPC等:

    本文将会按照官方文档,选取几种方式进行简单demo实现。

    三、TCP方式

    nestjs实现微服务主要依赖包@nestjs/microservices,所以在开始之前需要提前在项目中安装该依赖:$ npm i --save @nestjs/microservices

    首先准备两个nestjs项目,使用官方提供的脚手架工具进行项目构建:

    $ npm i -g @nestjs/cli
    $ nest new project-name
    

    项目名可以自定义,本文中暂时采用[项目一]与[项目二]进行描述。项目一为API与微服务混合模式,项目二为单微服务模式,前者为调用方,后者为服务功能提供方。

    在两个项目中安装@nestjs/microservices依赖后,即可开始代码编写。

    3.1 微服务模式(服务提供方)

    先改造[项目二],将src/main.ts文件改写为如下:

    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { Transport, MicroserviceOptions } from '@nestjs/microservices';
    
    async function bootstrap() {
      // const app = await NestFactory.create(AppModule);
      // await app.listen(3000);
      const app = await NestFactory.createMicroservice<MicroserviceOptions>(
        AppModule,
        {
          transport: Transport.TCP,
        },
      );
      app.listen();
    }
    bootstrap();
    

    对比改动前后的代码,可以发现app的生成方式发生的变化,更改前是通过NestFactory.create方法生成,更改后则是通过NestFactory.createMicroservice生成,并且多了相关的参数。

    前者是nestjs生成普通应用程序对象(INestApplication),该对象只启用了HTTP监听器,所以只能处理HTTP接口消息,而后者生成INestMicroservice对象,该对象可监听TCP消息,该对象即是框架实现TCP消息流转的入口。

    上述man文件只是提供了基础的能力支持,如果想体验具体功能,还需要编写相应的处理代码。

    打开文件src/app.controller.ts,这是工程默认生成的一个控制器(控制器主要服务接受请求与返回响应),可以看到已经使用@Get注解定义了一个接口。

    此时如果使用命令npm run start启动项目的话,使用请求发送工具(linux的curl命令或者postman等工具),发送get请求:http://localhost:3000,该请求是无法得到响应的。原因是我们在main.ts中实现的是微服务对象,该对象不支持HTTP监听。

    这里感觉框架的处理方式不太合理,如果是不支持的话,可以返回一些错误信息,或者在控制台打印日志,而不是一直让请求pending直到超时

    app.controller.ts中的@Get注解及其下方的函数实现先注释掉(不注释也可,没有影响)。

    实现一个微服务接收函数,在nestjs中,不管是TCP方式还是其他,都是通过使用注解@MessagePattern定义所有的微服务接口。将app.controller.ts文件改写为如下形式:

    import { Controller, Get } from '@nestjs/common';
    import { AppService } from './app.service';
    import { MessagePattern } from '@nestjs/microservices';
    
    @Controller()
    export class AppController {
      constructor(private readonly appService: AppService) {}
    
      // @Get()
      // getHello(): string {
      //   return this.appService.getHello();
      // }
    
      @MessagePattern('accumulate')
      accumulate(data: number[]): number {
        return (data || []).reduce((a, b) => a + b);
      }
    }
    

    在这里,我们定义了一个接口叫accumulate,接口作用是接收一个数字型数组,把所有元素相加后,将结果返回。

    至此,我们实现了一个很基础的微服务,这个微服务有个接口,接口功能是计算数组之和。但我们要怎么调用这个功能呢?前面在启动项目后,无法通过HTTP接口进行调用,这时候就需要使用另一个项目充当客户端的角色,对该功能进行调用使用了。

    也可以在这个项目里实现HTTP、TCP混合的应用程序,同时实现HTTP接口与TCP接口,进行相互调用。但这样就没有缺乏了微服务服务之间的味道了,所以不以这种例子作为说明

    3.2 HTTP服务(调用方)

    前面我们实现了一个微服务,接下来我们定义一个新的服务(项目一),对该微服务的功能进行调用。

    还是先关注入口文件main.ts,我们将监听的端口号换一下,例如改为10086

    不使用3000的原因是在上一个项目中已经占用了。虽然上一个项目中没有明确定义,但nestjs会给设置一个默认的端口号为3000
    端口号基本由开发人员自己定义,但不要与linux系统端口号或常见端口重复,如80、3679等,基本超过5位数的端口号使用的情况比较少

    接下来我们在程序中引入一个客户端,在app.module.ts中,改造为如下形式:

    import { Module } from '@nestjs/common';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { Transport, ClientsModule } from '@nestjs/microservices';
    
    @Module({
      imports: [
        ClientsModule.register([
          { name: 'MATH_SERVICE', transport: Transport.TCP },
        ]),
      ],
      controllers: [AppController],
      providers: [AppService],
    })
    export class AppModule {}
    

    对比改造前后,会发现我们在imports中引入了一个客户端,该客户端通过ClientsModule进行定义,名字叫做MATH_SERVICE,介质为Transport.TCP

    客户端名称可自定义,其目的也是在调用方有多个微服务时区分使用
    此时没有定义地址与端口号,采用默认的localhost与3000。如果服务不在同一环境,可添加options参数定义host与port

    接下来,还是改造app.controller.ts文件,改写为以下形式:

    import { Controller, Get, Inject } from '@nestjs/common';
    import { AppService } from './app.service';
    import { ClientProxy } from '@nestjs/microservices';
    import { Observable } from 'rxjs';
    
    @Controller()
    export class AppController {
      constructor(
        private readonly appService: AppService,
        @Inject('MATH_SERVICE') private readonly client: ClientProxy,
      ) {}
    
      @Get()
      getHello(): string {
        return this.appService.getHello();
      }
    
      @Get('testMicroservice')
      testMicroservice(): Observable<number> {
        return this.client.send('accumulate', [1, 2, 3, 4, 5]);
      }
    }
    

    对比更改前后,有两点地方值得注意:

    1. 使用@Inject注解引入名为MATH_SERVICE的客户端
    2. 实现一个get接口,名为testMicroservice

    上述引入的客户端即为app.module.ts中定义的客户端,而testMicroservice接口中,操作该客户端发送数据。

    发送数据的函数中,第一个参数为接收方的接口名(即为上一个项目中所定义的名称),第二个参数为发送的数据(即为上个项目中的接收参数)。

    Observable为观察者对象,在本文中并不重要,读者可自行了解

    运行项目:npm run start,可以看到控制台存在以下提示:

    该提示也表示在app.module.ts中的客户端已启用并被初始化。

    使用http请求工具,如curl http://localhost:10086/testMicroservice,可以得到请求响应,结果为15,即为传入数组的总和,说明我们服务之间调用成功。

    被调用方也要启动,要不然会请求不到,在控制台报错:connect ECONNREFUSED 127.0.0.1:3000

    至此,我们实现了一个微服务,并且定义了一个客户端,对该服务进行调用。

    四、与HTTP接口对比

    服务与服务之间相互调用,HTTP接口相对来说最为简单、调试最为方便,但是TCP在高并发情境下还是有一定的使用场景,高并发下,请求数量陡升,携带的信息差之毫厘,也会影响资源的使用,进而影响服务的速度,下面是分别使用HTTP方式与TCP方式,用wireshark工具进行抓包分析时,所展示的数据包大小:

    (前者为HTTP,后者为TCP。携带的数据都是{ data: [1,2,3,4,5] }

    从上面可以看到,当携带的信息较少时,TCP方式明显比HTTP方式所发送的数据包要小,HTTP数据包大小占比主要集中在请求头headers

    当请求中所发送的数据比较大,在数据包中的占比较大时,两种方式基本没区别。所以要看情况选择使用

    五、不同框架间通讯

    不同语言,不同框架之间数据接收、数据解析的方式可能不同,可以使用相对独立的介质,如gRPC、RabbitMQ等方式进行信息传递,通过单独的配置文件或者数据流转进行通讯。

  • 相关阅读:
    最大子数组
    链表插入排序
    链表求和
    有效回文串
    排球比赛计分系统
    超市收银系统
    三层架构
    Wpf+数据库代码封装+策略模式封装
    封装逻辑用策略模式实现
    代码封装
  • 原文地址:https://www.cnblogs.com/Mr-Kahn/p/15129631.html
Copyright © 2020-2023  润新知