在上一篇文章中,我们介绍了 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 中的微服务架构实现:
- 微服务基础配置
- 消息队列集成
- gRPC 服务实现
- 服务发现与注册
- 分布式配置管理
- 服务间通信方案
在下一篇文章中,我们将探讨 NestJS 的性能优化策略。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍