微服务架构设计与开发准备
- Direct Exchange(直连交换机)通过 Routing Key 精确路由消息:
- 当消息的 Routing Key 与队列绑定的 Binding Key 完全匹配时,消息会被投递到对应队列
- 例如:
- 订单微服务发送消息至餐厅微服务时,需指定 Routing Key 为
restaurant_route - 骑手微服务监听队列需绑定相同 Binding Key
- 订单微服务发送消息至餐厅微服务时,需指定 Routing Key 为
关键术语:
- Routing Key:消息的路由标识(如
order_created) - Binding Key:队列与交换机绑定的匹配规则(需与 Routing Key 一致)
核心目标:使用 RabbitMQ Direct Exchange 实现订单、餐厅、骑手微服务的协同通信
业务流程:用户下单 → 商家校验商品 → 分配骑手 → 状态流转(创建中 → 商户确认 → 骑手确认 → 结
算完成)
1 ) 微服务拆分
- 订单服务:核心业务服务,处理用户下单、订单状态查询等 RESTful 接口
- 新建订单
POST /orders、查询订单GET /orders/{id}
- 新建订单
- 餐厅服务:校验商品库存、接单状态管理
- 职责:校验商品库存、处理订单确认
- 骑手服务:分配骑手、更新配送状态
- 关键点:Direct Exchange 通过 routingKey 与 bindingKey 精确匹配实现消息路由
2 ) 业务流程关键阶段:
3 ) 消息路由机制
4 ) 环境配置:
安装 MySQL (Windows)
下载 MySQL Community Server (400MB+ 安装包) → 安装后任务栏出现 MySQL Notifier 图标
数据库初始化 (Navicat)
1. 新建连接:主机 localhost,端口 3306,用户/密码自定义
2. 创建数据库 `food_delivery`:
- 字符集:utf8mb4
- 排序规则:utf8mb4_general_ci
3. 执行建表 SQL(需提前准备商品表、商户表)
5 )数据表初始化
-- 商户表
CREATE TABLE `merchant` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL COMMENT '商户名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 商品表
INSERT INTO `product` (`id`, `name`, `price`)
VALUES (1, '珍珠奶茶', 15.00);
5 )NestJS 项目配置:
数据库连接配置(.env 文件):
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=your_password
DB_DATABASE=food_delivery
DB_SYNCHRONIZE=false # 生产环境禁用自动同步
应用启动配置(app.module.ts):
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Order } from './order/order.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [Order], // 引入实体类
synchronize: false, // 重要!生产环境需关闭
extra: {
charset: 'utf8mb4', // 修复字符集名称
charset: 'utf8mb4_general_ci',
timezone: '+08:00'
}
}),
],
})
export class AppModule {}
注意:
- 各微服务需监听不同端口(如订单服务
:3000,餐厅服务:3001) - 金额字段使用
DECIMAL(10,2)类型(避免浮点精度损失)
重点:
- 服务端口需唯一(例:订单服务
3000,餐厅服务3001,骑手服务3002) - 时区配置
+08:00避免时间戳错误
核心数据结构设计
采用分层模型确保数据一致性:
| 类型 | 作用 | 示例字段 |
|---|---|---|
| VO (View Object) | 前端交互数据 | accountId, address, productId |
| DTO (Data Transfer Object) | 微服务间消息体 | orderId, status, price (BigDecimal) |
| PO (Persistent Object) | 数据库实体 | 与表结构严格对应 |
1 ) VO (前端请求结构)
// order-create.vo.ts
export class OrderCreateVO {
accountId: number; // 用户ID
address: string; // 配送地址
productId: number; // 产品ID
}
2 ) DTO (消息传输结构)
// order-message.dto.ts
import { OrderStatus } from '../enum/order-status.enum';
export class OrderMessageDTO {
orderId: number; // 订单ID
status: OrderStatus; // 订单状态(枚举)
price: number; // 价格(避免浮点型,用整数分存储)
deliverymanId?: number; // 骑手ID(可选)
settlementId?: number; // 结算ID
pointsAwarded?: number; // 积分奖励
isConfirmed?: boolean; // 确认状态(必须为包装类,允许null)
}
// order-message.dto.ts
export class OrderMessageDTO {
orderId: number; // 订单ID
status: OrderStatus; // 订单状态(使用枚举)
price: number; // 订单金额(实际应用应使用 Decimal)
deliverymanId?: number; // 骑手ID(可为空)
productId: number; // 产品ID
userId: number; // 用户ID
settlementId?: number; // 结算ID
rewardPoints?: number; // 积分奖励数量
isConfirmed: boolean; // 确认状态(必须为包装类型 boolean)
}
3 )订单状态枚举
// order-status.enum.ts
export enum OrderStatus {
CREATING = 'CREATING', // 创建中
RESTAURANT_CONFIRMED = 'RESTAURANT_CONFIRMED', // 餐厅已确认
DELIVERYMAN_CONFIRMED = 'DELIVERYMAN_CONFIRMED',// 骑手已确认
SETTLEMENT_CONFIRMED = 'SETTLEMENT_CONFIRMED', // 结算完成
COMPLETED = 'COMPLETED', // 订单完成
FAILED = 'FAILED' // 订单失败
}
4 )PO (数据库实体)
// order.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { OrderStatus } from '../enum/order-status.enum';
@Entity()
export class Order {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'enum', enum: OrderStatus })
status: OrderStatus;
@Column()
accountId: number;
@Column({ type: 'decimal', precision: 10, scale: 2 })
price: number;
@Column({ type: 'timestamp' })
createdAt: Date;
}
关键设计原则:
- 金额存储:使用
decimal类型(或整数分)避免浮点精度问题 - 枚举映射:数据库用
ENUM或VARCHAR存储状态字符串,代码层强类型校验 - 字段命名:数据库下划线分隔(
account_id),代码驼峰命名(accountId) - 空值处理:DTO 中可选字段使用
?(如deliverymanId?)
工程示例:1
前置 RabbitMQ 配置命令:
创建 Direct Exchange
rabbitmqadmin declare exchange name=order_exchange type=direct durable=true arguments='{"x-message-ttl":60000}'
创建队列并绑定 Routing Key
rabbitmqadmin declare queue name=restaurant_queue durable=true
rabbitmqadmin declare queue name=deliveryman_queue durable=true
rabbitmqadmin declare binding source=order_exchange destination=restaurant_queue routing_key=restaurant
rabbitmqadmin declare binding source=order_exchange destination=deliveryman_queue routing_key=deliveryman
1 ) 方案 1:基础 Direct Exchange 通信
// order.service.ts
import { Injectable } from '@nestjs/common';
import { RabbitMQService } from '@golevelup/nestjs-rabbitmq';
import { OrderMessageDTO } from './dto/order-message.dto';
@Injectable()
export class OrderService {
constructor(private readonly rabbitmqService: RabbitMQService) {}
async notifyRestaurant(order: OrderMessageDTO) {
await this.rabbitmqService.publish('order_exchange', 'restaurant', {
...order,
status: OrderStatus.RESTAURANT_CONFIRMED,
});
}
}
// restaurant.consumer.ts
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
@Controller()
export class RestaurantConsumer {
@RabbitSubscribe({
exchange: 'order_exchange',
routingKey: 'restaurant',
queue: 'restaurant_queue',
})
handleOrderEvent(message: OrderMessageDTO) {
console.log('餐厅收到订单:', message.orderId);
// 业务逻辑:校验商品库存
}
}
2 ) 方案 2:双向确认机制(RPC 模式)
// 订单服务发送带回调的消息
async requestDeliveryman(order: OrderMessageDTO) {
const response = await this.rabbitmqService.request({
exchange: 'order_exchange',
routingKey: 'deliveryman',
payload: order,
});
console.log('骑手分配结果:', response);
}
// 骑手服务处理并回复
@RabbitSubscribe({
exchange: 'order_exchange',
routingKey: 'deliveryman',
queue: 'deliveryman_rpc_queue',
})
async assignDeliveryman(message: OrderMessageDTO) {
const assignedId = Math.floor(Math.random() * 100); // 模拟分配骑手
return { success: true, deliverymanId: assignedId }; // 自动回传结果
}
3 ) 方案 3:死信队列(DLX)保障可靠性
// 创建死信交换机和队列
rabbitmqadmin declare exchange name=dlx_exchange type=direct
rabbitmqadmin declare queue name=dead_letter_queue durable=true
rabbitmqadmin declare binding source=dlx_exchange destination=dead_letter_queue routing_key=dead_letter
// 订单队列绑定 DLX
rabbitmqadmin declare queue name=order_queue durable=true arguments='{"x-dead-letter-exchange":"dlx_exchange", "x-dead-letter-routing-key":"dead_letter"}'
// NestJS 消费端配置
@RabbitSubscribe({
exchange: 'order_exchange',
routingKey: 'order',
queue: 'order_queue',
allowNonJsonMessages: true,
queueOptions: { deadLetterExchange: 'dlx_exchange' },
})
async processOrder(message: OrderMessageDTO) {
if (message.status === OrderStatus.FAILED) {
throw new Error('触发死信'); // 异常消息自动转入死信队列
}
}
工程示例:2
1 ) RabbitMQ 配置与连接
安装依赖:
npm install @nestjs/microservices amqplib
RabbitMQ 连接配置(rabbitmq.config.ts):
import { RabbitMQConfig } from '@nestjs/microservices';
export const rabbitmqConfig: RabbitMQConfig = {
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'order_queue',
queueOptions: {
durable: true, // 持久化队列(服务重启后消息不丢失)
},
exchange: 'direct_exchange',
exchangeType: 'direct',
},
};
2 )消息生产者(订单微服务)
// order.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { OrderMessageDTO } from './dto/order-message.dto';
@Injectable()
export class OrderService {
constructor(
@Inject('RABBITMQ_SERVICE') private readonly client: ClientProxy,
) {}
async createOrder(createOrderDto: OrderCreateVO) {
// 1. 创建订单(数据库操作)
const order = await this.orderRepository.save(createOrderDto);
// 2. 发送消息至餐厅微服务
const message: OrderMessageDTO = {
orderId: order.id,
status: OrderStatus.CREATING,
productId: createOrderDto.productId,
userId: createOrderDto.accountId,
isConfirmed: false,
};
this.client.emit('restaurant_route', message); // Routing Key = restaurant_route
}
}
3 )消息消费者(餐厅微服务)
// restaurant.controller.ts
import { Controller } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';
import { OrderMessageDTO } from 'shared-dtos';
@Controller()
export class RestaurantController {
@EventPattern('restaurant_route') // 监听指定 Routing Key
async handleOrderEvent(@Payload() data: OrderMessageDTO) {
// 1. 校验商品库存
const isAvailable = await this.checkProductStock(data.productId);
// 2. 更新订单状态
if (isAvailable) {
await this.updateOrderStatus(data.orderId, OrderStatus.RESTAURANT_CONFIRMED);
// 通知骑手微服务
this.client.emit('delivery_route', { ...data, status: OrderStatus.RESTAURANT_CONFIRMED });
}
}
private async checkProductStock(productId: number): Promise<boolean> {
// 数据库查询逻辑
}
}
4 )RabbitMQ 管理命令示例
创建 Direct Exchange
rabbitmqadmin declare exchange name=direct_exchange type=direct durable=true
创建队列并绑定 Exchange
rabbitmqadmin declare queue name=restaurant_queue durable=true
rabbitmqadmin declare binding source=direct_exchange destination=restaurant_queue routing_key=restaurant_route
5 )容错与扩展设计
| 方案 | 适用场景 | 实现方式 |
|---|---|---|
| 消息重试 | 临时性网络故障 | NestJS 微服务模块内置 retry 策略(配置重试次数与间隔) |
| 死信队列(DLX) | 处理无法消费的消息 | 声明死信交换机,绑定异常队列,设置消息 TTL |
| 服务熔断 | 下游服务持续不可用 | 集成 @nestjs/circuit-breaker,监控错误率触发熔断 |
关键配置(rabbitmq.config.ts 扩展):
options: {
// ...其他配置
prefetchCount: 10, // 每次预取消息量(避免单节点过载)
noAck: false, // 手动确认消息(确保消费成功后才移除)
}
工程示例:3
1 ) 方案1:基础Direct Exchange实现
// order.service.ts
import { RabbitMQService } from '@golevelup/nestjs-rabbitmq';
@Injectable()
export class OrderService {
constructor(private readonly rabbitmq: RabbitMQService) {}
async createOrder(vo: OrderCreateVO) {
// 1. 创建本地订单
const order = await this.orderRepo.save({...vo, status: OrderState.CREATING});
// 2. 推送餐厅验证请求
this.rabbitmq.publish('order.direct', 'restaurant.verify', {
orderId: order.id,
productId: vo.productId
});
// 3. 推送骑手分配请求
this.rabbitmq.publish('order.direct', 'deliveryman.assign', {
orderId: order.id,
address: vo.address
});
}
}
// restaurant.service.ts
@RabbitRPC({
exchange: 'order.direct',
routingKey: 'restaurant.verify',
queue: 'restaurant_queue'
})
handleRestaurantVerify(msg: { orderId: number, productId: number }) {
// 验证商品库存
const result = await this.productService.verifyStock(msg.productId);
// 回传验证结果
this.rabbitmq.publish('order.direct', 'order.update', {
orderId: msg.orderId,
status: result ? OrderState.RESTAURANT_CONFIRMED : OrderState.FAILED
});
}
2 ) 方案2:消息持久化与ACK确认
// 消息发布配置
this.rabbitmq.publish('order.direct', 'deliveryman.assign',
{ orderId: 123 },
{
persistent: true, // 消息持久化
headers: { 'retry-count': 0 } // 自定义重试标记
}
);
// 消息消费配置
@RabbitSubscribe({
exchange: 'order.direct',
routingKey: 'order.update',
queue: 'order_update_queue',
queueOptions: {
durable: true, // 队列持久化
arguments: { 'x-message-ttl': 60000 } // 消息TTL
},
allowNonJsonMessages: true
})
async handleOrderUpdate(msg: any) {
try {
await this.orderService.updateStatus(msg.orderId, msg.status);
return { ack: true }; // 显式ACK确认
} catch (e) {
return { nack: true, requeue: msg.headers['retry-count'] < 3 }; // 限制重试
}
}
3 ) 方案3:死信队列与错误处理
// 创建死信交换器
await this.rabbitmq.createExchange({
name: 'order.dlx',
type: 'direct',
durable: true
});
// 队列绑定死信交换器
await this.rabbitmq.createQueue({
name: 'restaurant_queue',
options: {
durable: true,
deadLetterExchange: 'order.dlx', // 死信路由
deadLetterRoutingKey: 'error.restaurant'
}
});
// 死信处理器
@RabbitRPC({
exchange: 'order.dlx',
routingKey: 'error.*',
queue: 'error_handler_queue'
})
handleError(msg, headers) {
const service = headers['routingKey'].split('.')[1];
this.errorService.log({
service,
payload: msg,
timestamp: Date.now()
});
}
RabbitMQ 运维命令手册
1 ) 队列状态检查
rabbitmqctl list_queues name messages_ready messages_unacknowledged
2 )交换器绑定关系查询
rabbitmqctl list_bindings source_name source_kind destination_name routing_key
3 )消息积压告警
# 监控 restaurant_queue 消息积压
rabbitmqctl eval 'rabbitmq_management:get_queue_metrics("/", <<"restaurant_queue">>).messages_ready.'
4 )强制重试死信
rabbitmqadmin publish routing_key="restaurant" payload='{"orderId":1001}' \
exchange=dlx_exchange \
properties='{"headers": {"retry_count": 0}}'
关键优化实践
1 ) 消息确认机制
// 生产者消息确认
this.amqpConnection.publish('direct.order', 'order.create',
{ orderId: 1001 },
{ persistent: true, mandatory: true } // 开启持久化与路由失败回调
);
2 ) 消费者限流配置
@RabbitSubscribe({
exchange: 'direct.order',
routingKey: 'order.*',
queue: 'rider_queue',
queueOptions: {
channel: 'rider_channel',
prefetchCount: 5 // 每次处理5条消息
}
})
3 )错误重试策略
# .env
RABBIT_RETRY_INTERVAL=3000
RABBIT_MAX_RETRIES=5
架构验证要点
1 ) 使用 curl 测试订单创建:
curl -X POST http://localhost:3000/orders -d '{"productId":1, "userId":1001}'
2 ) 通过 RabbitMQ Management 插件(http://localhost:15672)观察消息流向
3 ) 数据库事务验证:订单状态与库存扣减的原子性操作
核心注意事项
1 ) 金融计算安全
订单金额必须使用Decimal类型(安装decimal.js库):
import { Decimal } from 'decimal.js';
const price = new Decimal(19.99);
const total = price.mul(2).add('0.01'); // 39.99
2 ) 跨服务ID一致性
使用分布式ID生成方案:
// 安装 snowflake-id(Twitter雪花算法)
import { Snowflake } from 'snowflake-id';
const snowflake = new Snowflake();
const orderId = snowflake.generate().toString();
3 )消息幂等性设计
@Entity()
export class ProcessedMessage {
@PrimaryColumn()
messageId: string; // 唯一消息ID
}
async handleMessage(msg) {
if (await this.messageRepo.exist(msg.id)) return;
// 业务处理...
this.messageRepo.save({ id: msg.id });
}
关键问题与优化策略
1 ) 消息序列化:
使用 JSON 序列化消息,配置 RabbitMQ 的 content_type 为 application/json
this.rabbitmqService.publish('exchange', 'routingKey', payload, {
contentType: 'application/json',
});
2 ) 连接池管理:
// main.ts 中初始化
const app = await NestFactory.createMicroservice(AppModule, {
strategy: new RabbitMQServer({
uri: 'amqp://localhost:5672',
connectionInitOptions: { wait: true },
connectionManagerOptions: { heartbeatInterval: 30 },
}),
});
3 )错误处理:
- 消息重试:通过
@RabbitRPC的retry选项实现指数退避重试。 - 死信队列:捕获未处理消息,人工介入排查。
4 ) 性能优化:
- 消息批处理:使用
batchSize参数批量消费消息。 - 通道复用:单个连接创建多通道(Channel)并行处理。
术语解释:
- Direct Exchange:根据
routingKey精确路由消息到队列。 - 死信队列(DLX):当消息消费失败或超时,自动转发到备用队列。
- BigDecimal:精确数值类型,避免金融场景的浮点误差(如用整数分存储金额)。
初学者指导:核心概念解析
| 术语 | 说明 |
|---|---|
| Direct Exchange | 通过精确匹配路由键投递消息,用于精准路由场景(如订单状态更新) |
| 消息持久化 | 通过persistent:true和队列durable:true双配置防止消息丢失 |
| 死信队列(DLX) | 处理多次重试失败的消息,避免阻塞主业务流 |
| RESTful | 资源导向的API设计风格,URI表示资源,HTTP方法表示操作类型 |
| UTC+8时区 | 中国标准时间(Asia/Shanghai),所有服务器必须统一时区配置 |
架构演进建议:
- 初期使用方案1快速实现核心流程
- 业务量增长后升级方案2确保消息可靠性
- 生产环境必须配置方案3的死信处理机制
- 消息体压缩(如gzip)可降低带宽占用30%以上
总结
本文通过以下步骤实现基于 Direct Exchange 的微服务通信:
- 消息路由机制:使用 RabbitMQ Direct Exchange 实现服务间精确路由。
- 领域模型设计:
- 严格区分 DTO(传输)、Entity(持久化)、VO(前端交互)。
- 枚举类型强化状态机流转(如
OrderStatus)。
- 工程实践:
- NestJS 集成 RabbitMQ 的模块化配置。
- 生产者-消费者模式的异步解耦。
- 容错保障:
- 死信队列处理异常消息。
- 熔断机制防止雪崩效应。
扩展建议:
- 结合 API 网关(如 Kong)统一管理微服务入口。
- 使用 TypeORM 迁移工具 管理数据库 Schema 变更。
- 集成 Prometheus + Grafana 监控消息队列吞吐量与延迟。
969

被折叠的 条评论
为什么被折叠?



