2. NestJS 微服务架构概览
在深入事务边界与补偿机制之前,必须先理解 NestJS 微服务的底层架构。本章将拆解微服务模块的核心组件、传输层选型以及三种主流通信模式,为后续的错误处理与补偿设计奠定基础。
2.1 微服务模块核心组件
NestJS 微服务通过 @nestjs/microservices 包实现。每个微服务实例本质上是一个独立的 NestJS 应用,通过 传输层 与外界通信。核心组件包括:
- ClientProxy:客户端代理,用于向微服务发送消息或事件。
- @MessagePattern():装饰器,定义请求-响应模式的处理器。
- @EventPattern():装饰器,定义事件驱动模式的处理器。
- Custom Transporters:自定义传输器,支持扩展非内置协议。
关键概念: 微服务模块不直接暴露 HTTP 端口,而是通过传输层监听特定协议(如 TCP、消息队列)。这使得服务间通信与外部 API 网关解耦。
2.2 传输层对比与选型
NestJS 内置了多种传输层策略。选择哪种传输层直接影响事务边界的可靠性、消息持久化能力以及补偿机制的实现复杂度。
| 传输层 | 通信模式 | 消息持久化 | 适用场景 | 事务补偿复杂度 |
|---|---|---|---|---|
| TCP | 请求-响应 / 事件 | 无(内存) | 内部低延迟、高吞吐、服务间直连 | 高(需自行实现重试与回滚) |
| RabbitMQ | 请求-响应 / 事件 / 发布-订阅 | 支持(队列持久化) | 异步任务、消息可靠投递、广播 | 中(借助死信队列与重试) |
| Kafka | 事件 / 流处理 | 支持(日志持久化) | 高吞吐事件流、日志聚合、数据管道 | 中(利用偏移量与幂等性) |
| Redis | 发布-订阅 | 无(内存) | 轻量级广播、实时通知 | 高(无持久化,补偿困难) |
| gRPC | 请求-响应(双向流) | 无 | 高性能、强类型、多语言互操作 | 高(需结合外部存储) |
选型建议: 对于涉及分布式事务的 API,优先选择支持消息持久化的传输层(RabbitMQ / Kafka)。TCP 和 Redis 适合无状态或可重试的幂等操作,否则补偿逻辑会非常脆弱。
2.3 微服务通信模式
NestJS 支持三种核心通信模式,每种模式对事务边界的影响不同:
- 请求-响应(Request-Response):客户端发送消息并等待响应。使用
@MessagePattern()和client.send()。适用于需要即时返回结果的同步调用,如查询订单状态。 - 事件驱动(Event-Driven):客户端发送事件,不等待响应。使用
@EventPattern()和client.emit()。适用于异步通知,如订单创建后触发库存扣减。 - 发布-订阅(Pub/Sub):基于消息队列(如 RabbitMQ 的 Topic Exchange 或 Kafka 的 Topic)实现一对多广播。适用于需要多个服务同时处理同一事件的场景,如用户注册后同步发送邮件和短信。
2.4 通信模式与事务边界的关系
不同通信模式对事务边界的影响差异显著:
- 请求-响应:事务边界通常与 HTTP 请求生命周期绑定。如果下游服务失败,上游可以立即感知并回滚本地事务。但跨服务回滚需要协调(如 Saga 模式)。
- 事件驱动:事务边界在事件发布时结束。下游消费者可能延迟处理,导致事务不一致。补偿机制需要依赖消息重试或死信队列。
- 发布-订阅:事务边界在事件发布到消息队列时结束。多个消费者独立处理,任何一个失败都不影响其他消费者。补偿需要每个消费者自行实现幂等性和回滚。
常见陷阱: 在事件驱动模式下,不要将数据库事务与消息发送放在同一个本地事务中。如果消息发送成功但数据库回滚,会导致“幽灵事件”。应使用“事务性发件箱”模式(Transactional Outbox)确保原子性。
2.5 微服务实例化与配置示例
以下是一个基于 TCP 传输层的微服务启动示例:
// main.ts - 微服务启动
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
host: '0.0.0.0',
port: 3001,
},
},
);
await app.listen();
}
bootstrap();
客户端连接示例:
// client.service.ts - 客户端代理
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';
@Injectable()
export class ClientService implements OnModuleInit {
private client: ClientProxy;
onModuleInit() {
this.client = ClientProxyFactory.create({
transport: Transport.TCP,
options: { host: 'localhost', port: 3001 },
});
}
async sendMessage(pattern: string, data: any) {
return this.client.send(pattern, data).toPromise();
}
}
生产建议: 在微服务架构中,建议将传输层配置(如 host、port、队列名称)统一放在环境变量或配置中心,避免硬编码。对于 RabbitMQ 和 Kafka,还需要配置连接池、重试策略和心跳检测。