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)实现一对多广播。适用于需要多个服务同时处理同一事件的场景,如用户注册后同步发送邮件和短信。
NestJS 微服务通信模式架构图 NestJS 微服务通信模式架构 API 网关 / 客户端 请求-响应服务 send() / 响应 事件驱动服务 emit() 事件 发布-订阅服务 emit() 广播 消息队列 RabbitMQ / Kafka 持久化 / 路由 / 重试 说明:请求-响应模式需要等待返回结果;事件驱动模式异步触发;发布-订阅模式通过消息队列实现一对多广播。

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,还需要配置连接池、重试策略和心跳检测。

2.6 本章小结