NestJS小技巧11-微服务之使用Nest.js处理gRPC服务

2,365 阅读3分钟
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

Nest.js中,我们有一些为微服务准备的传输层实现。其中之一是gRPC转运体,这无疑是最有趣的转运体之一。在本文中,我们将探讨这一层背后的想法,以及如何在NestJS中实现它。

在弄脏你的手并在NestJs中创建我们的微服务之前,如果你想了解更多关于微服务的信息,请访问下面的帖子:

gRPC

gRPC是由Google开发的一种高性能的远程过程调用(RPC)框架,它可以使客户端应用程序和服务器应用程序通过网络进行通信,以快速高效地交换数据和请求远程服务。

gRPC使用Protocol Buffers作为默认的序列化机制,它是一种语言无关的二进制格式,用于编码结构化数据。它支持多种编程语言,包括C ++,Java,Python和Go,并提供一元和流式RPC。

gRPC旨在轻量级、快速和可靠。它基于HTTP/2标准构建,可以提供高效的流控制、多路复用和头部压缩,从而在减少网络延迟的同时提高了吞吐量。此外,gRPC还支持各种身份验证和授权机制,例如基于TLS的身份验证、OAuth2和JWT。

通过使用gRPC,开发人员可以快速构建跨平台和跨语言的应用程序,同时获得高性能、可靠性和安全性。

使用gRPC有以下几个优点:

  1. 高性能:gRPC使用基于HTTP/2的传输协议,可以实现高效的流控制、多路复用和头部压缩,从而减少了网络延迟和带宽占用,提高了吞吐量。
  2. 省时省力:gRPC使用Protocol Buffers作为默认的序列化机制,可以将结构化数据转换为二进制格式,从而减少了数据传输的大小和时间,提高了效率。同时,gRPC提供了代码自动生成工具,可以自动生成客户端和服务端的代码,省去了手动编写代码的过程。
  3. 跨平台和跨语言:gRPC支持多种编程语言和平台,例如C++, Java, Python和Go等,使得开发人员可以在不同的语言和平台之间轻松地通信和交换数据。
  4. 可靠性:gRPC支持各种身份验证和授权机制,例如基于TLS的身份验证、OAuth2和JWT等,从而提高了系统的安全性和可靠性。

总之,使用gRPC可以帮助开发人员快速构建高性能、跨平台和可靠的分布式系统,提高开发效率和用户体验。

创建项目模型

详细内容参照官方文档,跟着官方的说明,我们来跟着做一下

1. 客户端

客户端需要说明的东西不多,有关的就是注册模型,其他的都和一般的NestJS一样。

  1. 安装必要的软件包
$ pnpm i @nestjs/microservices @grpc/proto-loader @grpc/grpc-js
  1. 使用Nestjs CLI创建了一个项目,并定义了一个名为hero的模块。
$ nest new client  
$ nest g resource hero --no-spec
# 选RestAPI, 不要CURD
  1. 现在让我们创建.proto文件,它的工作是描述我们想要远程使用的服务和方法。
syntax = "proto3";

package hero;

service HeroService {
  rpc FindOne (HeroById) returns (Hero);
  rpc FindMany (stream HeroById) returns (stream Hero);
}

message HeroById {
  int32 id = 1;
}

message Hero {
  int32 id = 1;
  string name = 2;
}
  1. 在hero模块中注册gRPC服务。
@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'HERO_PACKAGE',
        ...grpcClientOptions,
      },
    ]),
  ],
  controllers: [HeroController],
  providers: [HeroService]
})
export class HeroModule {}

gRPC配置信息

import { ClientOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';

export const grpcClientOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
    package: 'hero', // ['hero', 'hero2']
    protoPath: join(__dirname, './hero/hero.proto'), // ['./hero/hero.proto', './hero/hero2.proto']
    url: '0.0.0.0:5000'
  },
};

gRPC配置说明

参数说明
packageProtobuf包名称(与.proto文件中的包设置匹配)。必须
protoPath“.proto”文件的绝对(或相对于根目录)路径。必需
url连接url。格式为ip address/dns name:port(例如,“localhost:50051”)的字符串,用于定义传输程序建立连接的地址/端口。可选择的默认为“localhost:5000”
protoLoader用于加载.proto文件的实用程序的NPM包名称。可选。默认为“@grpc/proto-loader”
loader@grpc/proto-loader选项。它们提供了对“.proto”文件行为的详细控制。可选。请参见此处了解更多详细信息 
credentialsS服务器凭据。可选。点击此处阅读更多信息
  1. 在控制器中通过依赖注入引入gRPC服务
import { Controller, Get, Inject, OnModuleInit, Param } from '@nestjs/common';
import {
  ClientGrpc,
} from '@nestjs/microservices';
import { Observable, ReplaySubject } from 'rxjs';
import { toArray } from 'rxjs/operators';
import { HeroById } from './interfaces/hero-by-id.interface';
import { Hero } from './interfaces/hero.interface';


interface HeroService {
  findOne(data: HeroById): Observable<Hero>;
  findMany(upstream: Observable<HeroById>): Observable<Hero>;
}

@Controller('hero')
export class HeroController implements OnModuleInit {
  private readonly items: Hero[] = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Doe' },
  ];
  private heroService: HeroService;

  constructor(@Inject('HERO_PACKAGE') private readonly client: ClientGrpc) {}

  onModuleInit() {
    this.heroService = this.client.getService<HeroService>('HeroService');
  }

  @Get()
  getMany(): Observable<Hero[]> {
    const ids$ = new ReplaySubject<HeroById>();
    ids$.next({ id: 1 });
    ids$.next({ id: 2 });
    ids$.complete();

    const stream = this.heroService.findMany(ids$.asObservable());
    return stream.pipe(toArray());
  }

  @Get(':id')
  getById(@Param('id') id: string): Observable<Hero> {
    return this.heroService.findOne({ id: +id });
  }
}

2. gRPC服务

  1. 安装必要的软件包
$ pnpm i @nestjs/microservices @grpc/proto-loader @grpc/grpc-js
  1. 使用Nestjs CLI创建了一个项目,并定义了一个名为hero的模块。
$ nest new microservice1  
$ nest g resource hero --no-spec
# 选RestAPI, 不要CURD
  1. 让我们更改文件main.ts,创建一个微服务。grpcClientOptions和客户端的一样。
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
import { grpcClientOptions } from './grpc-client.options';

async function bootstrap() {
   const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
    ...grpcClientOptions
   });
   await app.listen();
}
bootstrap();
  1. 写gRPC服务,proto和客户端的一样。

一般的gRPC方法用装饰器@GrpcMethod装饰就可以使用了,具体传参的内容并不复杂,大家看官网说明就行了。

gRPC本身支持长期实时连接,通常称为流。流对于聊天、订阅或大量数据传输等情况很有用。点击此处查看官方文档中的更多详细信息。

Nest以两种可能的方式支持GRPC流处理程序:

  • RxJS Subject+Observable处理程序:可以在Controller方法内部编写响应,也可以传递给Subject/Observable使用者
  • 纯GRPC调用流处理程序:传递给某个执行器可能很有用,该执行器将处理节点标准“双工”流处理程序的其余调度。

gRPC的代码如下:

import { Controller } from '@nestjs/common';
import {
  GrpcMethod,
  GrpcStreamMethod,
} from '@nestjs/microservices';
import { Observable, Subject } from 'rxjs';
import { HeroById } from './interfaces/hero-by-id.interface';
import { Hero } from './interfaces/hero.interface';

@Controller('hero')
export class HeroController {
  private readonly items: Hero[] = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Doe' },
  ];

  @GrpcMethod('HeroService')
  findOne(data: HeroById): Hero {
    return this.items.find(({ id }) => id === data.id);
  }

  @GrpcStreamMethod('HeroService')
  findMany(data$: Observable<HeroById>): Observable<Hero> {
    const hero$ = new Subject<Hero>();

    const onNext = (heroById: HeroById) => {
      const item = this.items.find(({ id }) => id === heroById.id);
      hero$.next(item);
    };
    const onComplete = () => hero$.complete();
    data$.subscribe({
      next: onNext,
      complete: onComplete,
    });

    return hero$.asObservable();
  }
}

在客户端怎么调用流,请参照官方文档

3. 运行看效果

同时启动 微服务和客户端服务。并访问http://localhost:3000/hero/1,返回的结果如下。

{"id":1,"name":"John"}

结论

欢迎使用Nest.js开发gRPC微服务。如果觉得这篇文章对您有帮助,请点赞/评论。谢谢大家!

本章代码

代码

clientmicroservice1 是一组,micro-and-client是把服务和grpc服务放在一起写了。