第 22 章:微服务
学习目标
- 理解生产级微服务方案选型
- 用 RabbitMQ 做异步消息
- 用 gRPC 做高性能 RPC
- 避开 3 个微服务选型坑
一、生产级方案选型
不讲 TCP/Redis Transport(玩具级),只看生产主流。
| 场景 | 主流方案 | 选型理由 |
|---|---|---|
| 简单同步调用 | HTTP + REST | 简单、通用、调试容易 |
| 异步消息 | RabbitMQ / Kafka | 持久化、重试、死信 |
| 高性能同步 RPC | gRPC | 二进制、强类型、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 axiostypescript
// 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-manager3.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-loader4.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/REST | gRPC |
|---|---|---|
| 协议 | HTTP/1.1 + JSON | HTTP/2 + Protobuf(二进制) |
| 性能 | 中 | 高 5~10 倍 |
| 强类型 | OpenAPI/TS 推导 | .proto 强制 |
| 调试 | curl/浏览器 | grpcurl(麻烦) |
| 适合 | 内部 + 外部 | 纯内部 |
⚠️ 坑 3:gRPC 强加 Protobuf → 调试不友好,不是所有团队都适合。新项目先用 HTTP,真有性能瓶颈再换 gRPC。
五、生产级服务发现
写死 IP 不实际,生产用 Nacos/Consul 动态发现:
bash
pnpm add nacostypescript
// 启动时注册
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/terminustypescript
@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。