微服务是一种应用架构,它将每个应用功能都放在自己的服务中,与其他服务隔离。这些服务是松散耦合的,可独立部署。
这种架构的出现是为了解决旧的 Web 应用开发的单体方法。在单体软件中,所有的东西都是作为一个单元构建的,所有的业务逻辑都被归入一个广泛的应用。
这种方法使更新代码库的过程变得复杂化,因为它影响到整个系统,即使是小的代码改动也需要构建和部署整个软件的新版本。此外,哪怕你只想扩展应用的某个特定功能,却需要扩展整个应用来实现它。
微服务解决了单体系统所面临的这些挑战,它将应用从一个整体分割成几个小部分。
从本质上讲,微服务架构解决了庞大、复杂应用的快速开发问题。
对于“哪个更好?”这一问题,目前还没有通用的答案。答案取决于各种情况,因为每一种情况都有其好处和缺点。
下面是一些微服务架构的优点和缺点,你可能对此已经有所了解:
语言不可知性:微服务并不限于特定的编程语言,每个微服务都可以用不同的语言来编写,以支持选定的通信协议。
可扩展性:由于微服务和它的职责可以由开发者共同承担,所以如果有一个大的团队参与到这个项目中,应用就会变得更加易于维护。
无限迭代:由于开发者不会被其他组件所束缚,所以在微服务上迭代会变得更加简单。
单元测试:由于微服务是独立的应用,它的重点是特定的功能,因此,开发者可以很轻松地编写测试脚本,以验证该特定功能。
要作为一个整体来管理是很困难的:凯撒大帝有一句名言“分而治之”(divide et impera,拉丁语),即使在这里也可以大规模应用,但是要谨慎,因为过多的活动部分会变得难以管理。
难以追踪:如果架构变得过于复杂,微服务之间的通信渠道会非常多,出现错误后会很难追溯并确定故障点。
需要大量的专业知识:构建和部署微服务要求非常高的计划和协调方面的软技能。
具有挑战性的测试:测试是一把双刃剑,因为微服务作为一个整体更难测试。集成和端到端的测试同样会有挑战。
审计日志:可能更难获得和调查。
在架构方面,SaaS 微服务非常适合,因为微服务是 SaaS 应用的一个不错的选择。由于这类应用想要用户付钱买单,那么它就需要提供高可用的服务,因此将软件分成小块可以加快恢复速度。同时,SaaS 应用的发展主要是由其社区推动,所以,它也会受到很多变化的影响,而通过微服务和解耦,开发者可以获得了灵活性,这是单体架构无法提供的。
单体应用程序可能难以水平扩展,因为你必须复制整个应用程序,如果它依赖于单个数据库,这个过程将变得更加困难。另一边,微服务却可以根据单个服务进行扩展、复制或负载平衡。比如,如果你需要发送更多的电子邮件,你只需要扩展负责电子邮件功能的微服务。今天你有 10 个用户,明天你有 1000 个;SaaS 应用可以在短时间内维持大规模的增长,这就是为什么他们的架构必须要以经济的方式进行轻松扩展的原因。
这样还可以减少资源的消耗,因此可以减少账单。所以,可以肯定地说,微服务是 SaaS 企业架构的下一个阶段。
弄清你是否需要微服务的好方法是问自己:我有关于单体应用的问题吗?如果有的话,或许你应该考虑转向微服务。如果没有,那就坚持下去——没有必要把时间花在一个根本不存在的问题上。
由于服务之间彼此独立,所以与微服务的通信需要好好选择。通信协议的使用不当会造成应用的性能下降,大家必须根据自己应用的具体需求来选择通信协议。
有两种通信方式可以选择:同步通信和异步通信,这是请求 - 响应和基于事件的模式的基础。
在种情况下,即同步方式,客户端发送请求并等待响应。这种方法有一个缺陷,那就是它是一个阻塞模式。但是,如果你有一个读操作非常多的应用时,那就不一定了,因为你的应用更倾向从外部读取和接受信息。在这种情况下,使用同步方式可能是一个很好的选择,特别是当它涉及实时数据时。
我们的另一个选择是异步通信,这是一个非阻塞模式。如果你想要一种有弹性的微服务,那么,与同步通信相比,异步通信是一种更好的选择。在这种情况下,客户端会发送一个请求,收到请求的确认,并将其遗忘。这种方法适用于大量写操作、无法承受数据记录丢失的应用。
下面是一些涉及微服务通信的解决方案,你可以从中选择:
基于 HTTP 的 REST
基于 HTTP/2 的 REST
WebSocket
TCP 套接字
UDP 数据包
好好考虑适合自身需求的通信协议,因为这将使应用响应更快、效率更高。
在构建微服务时,有很多编程语言可供选择。NodeJS 就是其中之一。那么,为什么 NodeJS 是佳选择呢?
单线程 & 异步:NodeJS 使用事件循环来执行代码,允许异步代码被执行,从而使服务器能够使用非阻塞机制来响应。
事件驱动:NodeJS 使用事件驱动架构,该架构建立在软件开发的常见模式上,被称为发布 - 订阅或观察者模式,能够构建强大的应用,尤其是实时应用。
快速和高度的可扩展性:运行环境建立在强大的 JavaScript 引擎之一 V8 JavaScript Engine 之上,因此代码执行速度快,使得服务器能够同时处理多达 10000 个并发请求。
易于开发:创建多个微服务会导致重复的代码。Node.js 的微服务框架很容易创建,因为它抽象了大部分的底层系统。所以用这种编程语言创建一个微服务可以像写几行代码一样简单。
我们从创建用于用户管理的微服务开始,它将使用 TCP 数据包进行通信,并负责对用户进行 CRUD 操作。我们将使用 PacketSender 对其进行测试,PacketSender 是一个免费的工具,用于发送支持 TCP 的网络数据包。
微服务的架构和作用域被进一步界定。因此,从演示的角度来看,通过 HTTP 实现一个微服务与实现 NodeJS API 没有什么不同。
同时,通过 HTTP 来使用 REST 也很容易,但如果从这个协议切换到其他协议时,会出现一些问题。这也是本文中我们将会使用 TCP 包的异步模式来与微服务通信的原因。
我们将使用 NestJS 作为应用的框架。它并非 NodeJS 微服务框架,而是一个用于构建服务器端应用的框架。但是,由于其内置了多个微服务特性,使得工作变得更加容易。
用 Node.js 构建微服务相当容易,尤其是用 NestJS 框架。开始时,可以使用 CLI 创建一个新的 NestJS 应用,使用如下命令:
npx @nestjs/cli new user-microservice
该命令会创建并初始化一个新项目。要开始构建一个微服务,你需要安装以下软件包:
npm i --save @nestjs/microservices
后,为了让微服务启动和运行,我们需要用以下内容更新 main.ts 文件:
import { INestMicroservice } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const microservicesOptions: any = {
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8875,
},
};
const app: INestMicroservice = await NestFactory.createMicroservice(
AppModule,
microservicesOptions,
);
app.listen(() => console.log('Microservice is listening'));
}
bootstrap();
NestJS 支持几个内置的传输层实现,称为传输器。上面的代码将创建一个微服务,通过 TCP 传输层绑定到本地机器的 8875 端口进行通信。
我们可以使用消息模式或事件模式来与微服务通信。
消息模式的作用就像一个请求 - 响应方法,它适用于在服务之间交换消息,而当你只想发布事件而不等待响应时,就可以使用事件模式。
在我们的案例中,我们只实现根据给定的输入创建一个用户的功能,并且将获得创建的用户。因此,我们将在 app.controller.ts 文件中注册一个名为 create_user 的消息模式。
export class AppController {
constructor(private readonly appService: AppService) {}
async createUser( payload: CreateUserDto) {
const user = await this.appService.createUser(payload);
return user;
}
}
我们抽象出创建新用户的逻辑,因为它可以根据需求和使用的数据库以各种方式实现,我们将只关注与微服务相关的主题。
我们用来创建一个新用户的有效负载有以下格式:
import { IsString, IsEmail } from 'class-validator';
export class CreateUserDto {
()
email: string;
()
password: string;
}
一个带有 email 和 password 的简单对象
为了测试这个微服务,我们将使用 PacketSender 向应用发送一个 TCP 包。为此,将地址和端口设置为 127.0.0.1:8875,并从右侧的下拉菜单中选择 TCP。要对我们的信息进行编码,请使用 ASCII 字段,并用以下值来完成:
122
"data":{"email":"d@gmail.com","password":"12345678"},
"id":"ce51ebd3-32b1-4ae6-b7ef-e018126c4cc4"}
pattern:是我们正在寻找的信息,create_user。
data:是我们要发送的 JSON 对象,一个带有 email 和 password 的对象。
值 122 代表我们的消息的长度,从个大括号开始到后一个大括号(包括两个)。
数据包发送器配置
如果我们点击 Send 按钮,我们会看到如下日志:
日志活动
第二个是我们发送给微服务的内容,个是我们收到的内容。里面的响应是由我们的微服务返回的对象,即被创建的用户。
现在我们有了微服务,并进行了快速测试,看它是否能接收请求并返回响应,现在是时候创建一个 API 网关并将其连接到微服务上了。
为此,我们将使用上面描述的相同步骤创建一个新的 NestJS 应用,然后用以下内容更新 app.module.ts 文件。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigService } from "./config/config.service";
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: 'USER_MICROSERVICE',
useFactory: (configService: ConfigService) => {
const options = {
transport: Transport.TCP,
options: {
host: configService.get('USERS_MICROSERVICE_HOST'),
port: Number(configService.get('USERS_MICROSERVICE_PORT')),
},
};
return ClientProxyFactory.create(options as ClientOptions);
},
inject: [ConfigService],
},
AppService,
],
})
export class AppModule {}
我们将使用 .env 文件,我们将在其中存储任何与配置有关的值。这些文件将在一个配置服务的帮助下被读取。该微服务可以在 host 127.0.0.1:8875 处找到,其中 port 为 8875。
通过上面的代码,我们使用 ClientProxy 注入一个新的对象,代表与我们的用户 - 微服务的连接。这个 NestJS 类提供了几个内置的工具来与远程微服务交换信息。
为了使用这个链接对象,我们可以在 AppController 或 AppService 中注入它,如下所示:
export class AppController {
constructor(
private readonly client: ClientProxy,
private readonly appService: AppService
) {}
async createUser( payload: CreateUserDto) {
return this.client.send('create_user', payload).toPromise();
}
}
现在,每次 API 在路由 create-user 处受到 POST 请求时,API 网关将把请求和有效载荷一起转发给微服务,然后从微服务返回响应给用户。
原文链接:
https://frontegg.com/blog/implementing-microservices-in-nodejs