在上一篇文章中,我们介绍了 NestJS 的请求处理流程。本文将深入探讨如何使用 NestJS 构建微服务架构,实现从单体应用到分布式系统的转变。

微服务基础配置

1. 安装依赖

# 安装微服务相关依赖
npm install @nestjs/microservices
# 安装消息队列依赖
npm install amqplib @nestjs/microservices @golevelup/nestjs-rabbitmq
# 安装 gRPC 依赖
npm install @grpc/grpc-js @grpc/proto-loader

2. 微服务配置

// src/config/microservice.config.ts
import { Transport } from '@nestjs/microservices';

export const microserviceConfig = {
  // RabbitMQ 配置
  rabbitmq: {
    transport: Transport.RMQ,
    options: {
      urls: [process.env.RABBITMQ_URL || 'amqp://localhost:5672'],
      queue: 'main_queue',
      queueOptions: {
        durable: true,
      },
    },
  },
  
  // gRPC 配置
  grpc: {
    transport: Transport.GRPC,
    options: {
      package: 'hero',
      protoPath: join(__dirname, '../proto/hero.proto'),
      url: 'localhost:5000',
    },
  },
  
  // TCP 配置
  tcp: {
    transport: Transport.TCP,
    options: {
      host: 'localhost',
      port: 4000,
    },
  },
};

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
import { microserviceConfig } from './config/microservice.config';

async function bootstrap() {
  // 创建 HTTP 应用
  const app = await NestFactory.create(AppModule);
  
  // 注册微服务
  app.connectMicroservice<MicroserviceOptions>(microserviceConfig.rabbitmq);
  app.connectMicroservice<MicroserviceOptions>(microserviceConfig.grpc);
  
  // 启动所有微服务
  await app.startAllMicroservices();
  // 启动 HTTP 服务
  await app.listen(3000);
}
bootstrap();

消息队列集成

1. RabbitMQ 集成

// src/modules/orders/orders.module.ts
import { Module } from '@nestjs/common';
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { OrdersController } from './orders.controller';
import { OrdersService } from './orders.service';

@Module({
  imports: [
    RabbitMQModule.forRoot(RabbitMQModule, {
      exchanges: [
        {
          name: 'orders',
          type: 'topic',
        },
      ],
      uri: process.env.RABBITMQ_URL,
      connectionInitOptions: { wait: false },
    }),
  ],
  controllers: [OrdersController],
  providers: [OrdersService],
})
export class OrdersModule {}

// src/modules/orders/orders.service.ts
import { Injectable } from '@nestjs/common';
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { CreateOrderDto } from './dto/create-order.dto';

@Injectable()
export class OrdersService {
  constructor(private readonly amqpConnection: AmqpConnection) {}

  async createOrder(createOrderDto: CreateOrderDto) {
    // 发布订单创建事件
    await this.amqpConnection.publish(
      'orders',
      'order.created',
      {
        orderId: createOrderDto.id,
        userId: createOrderDto.userId,
        amount: createOrderDto.amount,
        timestamp: new Date(),
      },
    );
    
    return { message: 'Order created successfully' };
  }

  // 消费订单事件
  @RabbitSubscribe({
    exchange: 'orders',
    routingKey: 'order.*',
    queue: 'order-processing-queue',
  })
  async handleOrderEvent(msg: any) {
    console.log('Received order event:', msg);
    // 处理订单事件
    await this.processOrder(msg);
  }
}

2. Kafka 集成

// src/modules/events/events.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { EventsController } from './events.controller';
import { EventsService } from './events.service';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'KAFKA_SERVICE',
        transport: Transport.KAFKA,
        options: {
          client: {
            clientId: 'events',
            brokers: ['localhost:9092'],
          },
          consumer: {
            groupId: 'events-consumer',
          },
        },
      },
    ]),
  ],
  controllers: [EventsController],
  providers: [EventsService],
})
export class EventsModule {}

// src/modules/events/events.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { ClientKafka } from '@nestjs/microservices';

@Injectable()
export class EventsService {
  constructor(
    @Inject('KAFKA_SERVICE')
    private readonly kafkaClient: ClientKafka,
  ) {}

  async publishEvent(event: any) {
    return this.kafkaClient.emit('events', event);
  }

  @MessagePattern('events')
  async handleEvent(event: any) {
    console.log('Received event:', event);
    // 处理事件
    await this.processEvent(event);
  }
}

gRPC 服务实现

1. Proto 文件定义

// src/proto/hero.proto
syntax = "proto3";

package hero;

service HeroService {
  rpc FindOne (HeroById) returns (Hero) {}
  rpc FindAll (Empty) returns (Heroes) {}
}

message HeroById {
  int32 id = 1;
}

message Hero {
  int32 id = 1;
  string name = 2;
  string power = 3;
}

message Heroes {
  repeated Hero heroes = 1;
}

message Empty {}

2. gRPC 服务实现

// src/modules/heroes/heroes.controller.ts
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { HeroesService } from './heroes.service';

@Controller()
export class HeroesController {
  constructor(private readonly heroesService: HeroesService) {}

  @GrpcMethod('HeroService', 'FindOne')
  async findOne(data: { id: number }) {
    return this.heroesService.findOne(data.id);
  }

  @GrpcMethod('HeroService', 'FindAll')
  async findAll() {
    return { heroes: await this.heroesService.findAll() };
  }
}

// src/modules/heroes/heroes.service.ts
import { Injectable } from '@nestjs/common';
import { Hero } from './interfaces/hero.interface';

@Injectable()
export class HeroesService {
  private readonly heroes: Hero[] = [
    { id: 1, name: 'John', power: 'Time Control' },
    { id: 2, name: 'Alice', power: 'Telekinesis' },
  ];

  async findOne(id: number): Promise<Hero> {
    return this.heroes.find(hero => hero.id === id);
  }

  async findAll(): Promise<Hero[]> {
    return this.heroes;
  }
}

服务发现注册

1. Consul 集成

// src/modules/discovery/consul.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import * as Consul from 'consul';

@Injectable()
export class ConsulService implements OnModuleInit {
  private readonly consul: Consul.Consul;
  
  constructor() {
    this.consul = new Consul({
      host: process.env.CONSUL_HOST || 'localhost',
      port: process.env.CONSUL_PORT || '8500',
    });
  }

  async onModuleInit() {
    // 注册服务
    await this.registerService();
    // 健康检查
    await this.registerHealthCheck();
  }

  private async registerService() {
    await this.consul.agent.service.register({
      name: process.env.SERVICE_NAME,
      id: process.env.SERVICE_ID,
      address: process.env.SERVICE_HOST,
      port: parseInt(process.env.SERVICE_PORT),
      tags: ['nestjs', 'microservice'],
      check: {
        http: `http://${process.env.SERVICE_HOST}:${process.env.SERVICE_PORT}/health`,
        interval: '10s',
      },
    });
  }

  async discoverService(serviceName: string): Promise<string> {
    const services = await this.consul.catalog.service.nodes(serviceName);
    if (services.length === 0) {
      throw new Error(`Service ${serviceName} not found`);
    }
    // 简单的负载均衡:随机选择一个服务实例
    const service = services[Math.floor(Math.random() * services.length)];
    return `http://${service.ServiceAddress}:${service.ServicePort}`;
  }
}

2. 服务健康检查

// src/modules/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { TypeOrmHealthIndicator } from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      // 数据库健康检查
      () => this.db.pingCheck('database'),
      // 自定义健康检查
      () => this.checkServiceHealth(),
    ]);
  }

  private async checkServiceHealth() {
    // 实现自定义健康检查逻辑
    return {
      service: {
        status: 'up',
        details: {
          version: process.env.SERVICE_VERSION,
          uptime: process.uptime(),
        },
      },
    };
  }
}

分布式配置

1. 配置中心集成

// src/config/config.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV}`,
      validationSchema: Joi.object({
        NODE_ENV: Joi.string()
          .valid('development', 'production', 'test')
          .default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_URL: Joi.string().required(),
        RABBITMQ_URL: Joi.string().required(),
        KAFKA_BROKERS: Joi.string().required(),
        CONSUL_HOST: Joi.string().required(),
        CONSUL_PORT: Joi.number().required(),
      }),
    }),
  ],
  providers: [ConfigService],
  exports: [ConfigService],
})
export class AppConfigModule {}

2. 动态配置更新

// src/config/dynamic-config.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ConsulService } from '../discovery/consul.service';
import { Subject } from 'rxjs';

@Injectable()
export class DynamicConfigService implements OnModuleInit {
  private configSubject = new Subject<any>();
  private config: any = {};

  constructor(private readonly consulService: ConsulService) {}

  async onModuleInit() {
    // 初始化配置
    await this.loadConfig();
    // 监听配置变更
    this.watchConfigChanges();
  }

  private async loadConfig() {
    const configKeys = ['database', 'rabbitmq', 'kafka'];
    for (const key of configKeys) {
      const value = await this.consulService.kv.get(`config/${key}`);
      this.config[key] = JSON.parse(value);
    }
    this.configSubject.next(this.config);
  }

  private watchConfigChanges() {
    this.consulService.watch({
      method: this.consulService.kv.get,
      options: { key: 'config/', recurse: true },
    }).on('change', async () => {
      await this.loadConfig();
    });
  }

  getConfig<T>(key: string): T {
    return this.config[key];
  }

  onConfigUpdate() {
    return this.configSubject.asObservable();
  }
}

服务间通信

1. HTTP 客户端

// src/modules/http/http.service.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConsulService } from '../discovery/consul.service';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class ApiService {
  constructor(
    private readonly httpService: HttpService,
    private readonly consulService: ConsulService,
  ) {}

  async callService(serviceName: string, path: string, method = 'GET', data?: any) {
    // 服务发现
    const serviceUrl = await this.consulService.discoverService(serviceName);
    
    // 构建请求
    const request = {
      method,
      url: `${serviceUrl}${path}`,
      ...(data && { data }),
    };
    
    try {
      const response = await firstValueFrom(this.httpService.request(request));
      return response.data;
    } catch (error) {
      // 处理错误,可能需要重试或熔断
      this.handleError(error);
    }
  }

  private handleError(error: any) {
    // 实现错误处理逻辑
    if (error.response) {
      // 服务端错误
      throw new Error(`Service error: ${error.response.status}`);
    } else if (error.request) {
      // 网络错误
      throw new Error('Network error');
    } else {
      // 其他错误
      throw error;
    }
  }
}

2. 事件总线

// src/modules/events/event-bus.service.ts
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

@Injectable()
export class EventBusService {
  constructor(private eventEmitter: EventEmitter2) {}

  async publish(event: string, payload: any) {
    this.eventEmitter.emit(event, payload);
  }

  async subscribe(event: string, callback: (payload: any) => void) {
    this.eventEmitter.on(event, callback);
  }
}

// 使用示例
@Injectable()
export class OrderService {
  constructor(private eventBus: EventBusService) {}

  async createOrder(order: any) {
    // 创建订单
    const newOrder = await this.orderRepository.save(order);
    
    // 发布事件
    await this.eventBus.publish('order.created', newOrder);
    
    return newOrder;
  }
}

@Injectable()
export class NotificationService {
  constructor(private eventBus: EventBusService) {
    // 订阅事件
    this.eventBus.subscribe('order.created', this.handleOrderCreated.bind(this));
  }

  private async handleOrderCreated(order: any) {
    // 处理订单创建事件
    await this.sendNotification(order);
  }
}

写在最后

本文详细介绍了 NestJS 中的微服务架构实现:

  1. 微服务基础配置
  2. 消息队列集成
  3. gRPC 服务实现
  4. 服务发现与注册
  5. 分布式配置管理
  6. 服务间通信方案

在下一篇文章中,我们将探讨 NestJS 的性能优化策略。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍