RabbitMQ: 基于 Direct Exchange 的微服务通信与数模设计

微服务架构设计与开发准备


  • Direct Exchange(直连交换机)通过 Routing Key 精确路由消息:
    • 当消息的 Routing Key 与队列绑定的 Binding Key 完全匹配时,消息会被投递到对应队列
  • 例如:
    • 订单微服务发送消息至餐厅微服务时,需指定 Routing Key 为 restaurant_route
    • 骑手微服务监听队列需绑定相同 Binding 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 ) 消息路由机制

routingKey=order.create
bindingKey=order.create
bindingKey=order.dispatch
订单服务
Direct Exchange
餐厅服务队列
骑手服务队列

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 类型(或整数分)避免浮点精度问题
  • 枚举映射:数据库用 ENUMVARCHAR 存储状态字符串,代码层强类型校验
  • 字段命名:数据库下划线分隔(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_typeapplication/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 )错误处理:

  • 消息重试:通过 @RabbitRPCretry 选项实现指数退避重试。
  • 死信队列:捕获未处理消息,人工介入排查。

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 的微服务通信:

  1. 消息路由机制:使用 RabbitMQ Direct Exchange 实现服务间精确路由。
  2. 领域模型设计:
    • 严格区分 DTO(传输)、Entity(持久化)、VO(前端交互)。
    • 枚举类型强化状态机流转(如 OrderStatus)。
  3. 工程实践:
    • NestJS 集成 RabbitMQ 的模块化配置。
    • 生产者-消费者模式的异步解耦。
  4. 容错保障:
    • 死信队列处理异常消息。
    • 熔断机制防止雪崩效应。

扩展建议:

  • 结合 API 网关(如 Kong)统一管理微服务入口。
  • 使用 TypeORM 迁移工具 管理数据库 Schema 变更。
  • 集成 Prometheus + Grafana 监控消息队列吞吐量与延迟。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值