Skip to content
第 22 章 后端 ⏱ 12 分钟阅读

第 22 章:微服务 ​

学习目标 ​

  • 理解生产级微服务方案选型
  • 用 RabbitMQ 做异步消息
  • 用 gRPC 做高性能 RPC
  • 避开 3 个微服务选型坑

一、生产级方案选型 ​

不讲 TCP/Redis Transport(玩具级),只看生产主流。

场景主流方案选型理由
简单同步调用HTTP + REST简单、通用、调试容易
异步消息RabbitMQ / Kafka持久化、重试、死信
高性能同步 RPCgRPC二进制、强类型、HTTP/2
服务发现Consul / Nacos健康检查、负载均衡

NestJS 微服务为什么更适合异步:

  • HTTP 服务间调用 → 用 axios/fetch 直接发 HTTP(Spring Cloud Feign 同理)
  • 异步消息 → 用 RabbitMQ/Kafka(经典 MQ)
  • 强类型同步 → 用 gRPC

下面讲生产真用的两种。

二、HTTP 服务间调用(最简单) ​

最简单场景:服务 A 直接调服务 B 的 HTTP 接口,跟外部调用一样。

bash
pnpm add @nestjs/axios axios
typescript
// app.module.ts
import { HttpModule } from '@nestjs/axios';

@Module({
  imports: [
    HttpModule.register({
      timeout: 5000,
      baseURL: 'http://user-service:3001',           // B 服务地址
    }),
  ],
})
export class OrderModule {}
typescript
// order.service.ts
@Injectable()
export class OrderService {
  constructor(private readonly http: HttpService) {}

  async getUserInfo(userId: number) {
    const { data } = await firstValueFrom(
      this.http.get(`/users/${userId}`),             // 调 B 服务
    );
    return data;
  }
}

生产建议:用服务发现(Nacos/Consul)而非写死 IP:

typescript
HttpModule.registerAsync({
  inject: [ConfigService],
  useFactory: (cfg) => ({
    baseURL: cfg.get('USER_SERVICE_URL'),            // 从配置中心/环境变量读
    timeout: 5000,
  }),
});

⚠️ 坑 1:HTTP 调用不加超时 → 服务 B 卡住时服务 A 跟着卡,雪崩。

三、消息队列:RabbitMQ(异步主流) ​

典型场景:订单服务发"订单已创建"消息,邮件服务、库存服务、积分服务订阅。

bash
pnpm add @nestjs/microservices amqplib amqp-connection-manager

3.1 生产者(订单服务) ​

typescript
// order.module.ts
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'RABBITMQ_SERVICE',
        transport: Transport.RMQ,
        options: {
          urls: ['amqp://admin:secret@rabbitmq:5672'],
          queue: 'orders_queue',                     // 队列名
          queueOptions: { durable: true },           // 持久化
        },
      },
    ]),
  ],
})
export class OrderModule {}
typescript
// order.service.ts
@Injectable()
export class OrderService {
  constructor(@Inject('RABBITMQ_SERVICE') private readonly mq: ClientProxy) {}

  async create(dto: CreateOrderDto) {
    const order = await this.repo.save(dto);

    this.mq.emit('order.created', {                 // 发事件(单向)
      orderId: order.id,
      userId: order.userId,
      amount: order.amount,
    });

    return order;
  }
}

3.2 消费者(邮件服务) ​

typescript
// mail.module.ts
import { MicroserviceOptions, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'RABBITMQ_SERVICE',
        transport: Transport.RMQ,
        options: {
          urls: ['amqp://admin:secret@rabbitmq:5672'],
          queue: 'orders_queue',
        },
      },
    ]),
  ],
})
export class MailModule {}
typescript
// main.ts(邮件服务启动)
const app = await NestFactory.createMicroservice<MicroserviceOptions>(MailModule, {
  transport: Transport.RMQ,
  options: {
    urls: ['amqp://admin:secret@rabbitmq:5672'],
    queue: 'orders_queue',                          // 订阅同一个队列
  },
});
await app.listen();
typescript
// mail.controller.ts(消费端)
@Controller()
export class MailController {
  @EventPattern('order.created')                    // 监听事件
  async handleOrderCreated(@Payload() data: any) {
    await this.mailService.send(data.userId, `订单 ${data.orderId} 创建成功`);
  }
}

emit vs send:

方法用途场景
emit('event', data)发完就忘,不返回结果✅ 异步通知(主流)
send('msg', data)等结果(请求-响应)偶尔用(性能差)

3.3 死信队列(失败重试) ​

typescript
ClientsModule.register([
  {
    name: 'RABBITMQ_SERVICE',
    transport: Transport.RMQ,
    options: {
      urls: ['amqp://admin:secret@rabbitmq:5672'],
      queue: 'orders_queue',
      queueOptions: {
        durable: true,
        deadLetterExchange: 'orders_dlx',           // 失败进死信队列
        deadLetterRoutingKey: 'orders_dlq',
      },
    },
  },
]);

⚠️ 坑 2:消息没设持久化(durable: false)→ RabbitMQ 重启消息全丢。

四、gRPC(高性能 RPC) ​

典型场景:内部服务,要高性能、强类型契约。

bash
pnpm add @nestjs/microservices @grpc/grpc-js @grpc/proto-loader

4.1 定义 proto ​

protobuf
// proto/user.proto
syntax = "proto3";

package user;

service UserService {
  rpc GetUser(GetUserRequest) returns (UserResponse);
}

message GetUserRequest {
  int32 id = 1;
}

message UserResponse {
  int32 id = 1;
  string username = 2;
  string email = 3;
}

4.2 服务端 ​

typescript
// user.grpc.controller.ts
import { GrpcMethod } from '@nestjs/microservices';
import { join } from 'path';

@Controller()
export class UserGrpcController {
  @GrpcMethod('UserService', 'GetUser')              // 对应 proto
  async getUser(data: { id: number }) {
    const user = await this.repo.findOne({ where: { id: data.id } });
    return {
      id: user.id,
      username: user.username,
      email: user.email,
    };
  }
}
typescript
// main.ts(user 服务)
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
  transport: Transport.GRPC,
  options: {
    package: 'user',
    protoPath: join(__dirname, 'proto/user.proto'),
    url: '0.0.0.0:50051',
  },
});
await app.listen();

4.3 客户端 ​

typescript
// order.module.ts
ClientsModule.register([
  {
    name: 'USER_PACKAGE',
    transport: Transport.GRPC,
    options: {
      package: 'user',
      protoPath: join(__dirname, 'proto/user.proto'),
      url: 'user-service:50051',
    },
  },
])
typescript
// order.service.ts
@Injectable()
export class OrderService {
  constructor(@Inject('USER_PACKAGE') private readonly user: ClientGrpc) {}

  async getUser(id: number) {
    const svc = this.user.getService<UserService>('UserService');
    return firstValueFrom(svc.getUser({ id }));     // 返回 Observable
  }
}

gRPC vs HTTP 对比:

维度HTTP/RESTgRPC
协议HTTP/1.1 + JSONHTTP/2 + Protobuf(二进制)
性能中高 5~10 倍
强类型OpenAPI/TS 推导.proto 强制
调试curl/浏览器grpcurl(麻烦)
适合内部 + 外部纯内部

⚠️ 坑 3:gRPC 强加 Protobuf → 调试不友好,不是所有团队都适合。新项目先用 HTTP,真有性能瓶颈再换 gRPC。

五、生产级服务发现 ​

写死 IP 不实际,生产用 Nacos/Consul 动态发现:

bash
pnpm add nacos
typescript
// 启动时注册
await nacos_client.registerInstance('order-service', {
  ip: '192.168.1.10',
  port: 3000,
  healthy: true,
});

// 调用方从 Nacos 拿地址(不是写死)
const instances = await nacos_client.discover('user-service');
const target = instances[0];                        // 负载均衡选一个
await axios.get(`http://${target.ip}:${target.port}/users/${id}`);

六、健康检查(/health) ​

bash
pnpm add @nestjs/terminus
typescript
@Controller('health')
export class HealthController {
  constructor(
    private readonly health: HealthCheckService,
    private readonly db: TypeOrmHealthIndicator,
  ) {}

  @Get()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),           // DB 活着?
    ]);
  }
}

负载均衡器 / K8s 定期调 /health,挂了自动从服务列表剔除。

七、本章小结 ​

要点生产选型
简单同步HTTP + 服务发现
异步消息RabbitMQ / Kafka(持久化 + 死信)
高性能同步gRPC(Protobuf)
服务发现Nacos / Consul
健康检查@nestjs/terminus
不用TCP Transport(只 demo)/ Redis Transport(易丢消息)

记忆口诀:新项目从 HTTP 起,有瓶颈换 gRPC,异步加 RabbitMQ。

本站基于 VitePress 构建 · 由 StackHub 团队维护