RabbitMQ: 分布式事务消息处理框架之实现可靠消息方案 —— 枚举定义、实体建模、存储层实现与定时任务调度

核心组件实现

1 ) 枚举定义(MessageType)

定义消息类型(发送、接收、死信):

typescript 复制代码
// src/enums/message-type.enum.ts  
export enum MessageType {  
  SEND = 'SEND',      // 发送消息  
  RECEIVE = 'RECEIVE', // 接收消息  
  DEAD = 'DEAD'        // 死信消息  
}  

此枚举映射数据库的 type 字段,确保消息状态可追溯

关键点:

  • 严格匹配数据库字段:确保枚举值与数据库存储一致。
  • 语义明确:避免缩写(如RECEIVE非RECV)。

2 ) 实体建模(TransMessage)

使用TypeORM定义消息实体,对应数据库表trans_message

typescript 复制代码
// src/entities/trans-message.entity.ts  
import { Entity, PrimaryColumn, Column, Index } from 'typeorm';  
import { MessageType } from '../enums/message-type.enum';  
 
@Entity({ name: 'trans_message' })  
export class TransMessage {  
  @PrimaryColumn({ type: 'varchar', length: 36 })  
  id: string; // UUID确保全局唯一  
 
  @PrimaryColumn({ name: 'service_name', type: 'varchar', length: 50 })  
  serviceName: string; // 服务名称(联合主键)  
 
  @Column({ type: 'enum', enum: MessageType })  
  type: MessageType; // 消息类型(枚举)  
 
  @Column({ type: 'varchar', length: 100 })  
  exchange: string; // RabbitMQ交换机名  
 
  @Column({ name: 'routing_key', type: 'varchar', length: 100 })  
  routingKey: string; // RabbitMQ路由键  
 
  @Column({ type: 'varchar', length: 100 })  
  queue: string; // 队列名称  
 
  @Column({ type: 'int', default: 0 })  
  sequence: number; // 发送次数(用于重试)  
 
  @Column({ type: 'text' })  
  payload: string; // 消息内容(JSON序列化)  
 
  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })  
  createdAt: Date; // 创建时间  
}  

关键点:

  • 联合主键:@PrimaryColumn修饰idserviceName
  • 字段映射:@Column({ name: 'routing_key' })解决数据库下划线命名问题。
  • 默认值:sequence初始为0,createdAt自动生成时间戳。

3 ) 存储层实现(Repository/DAO)

通过TypeORM Repository封装数据库操作:

typescript 复制代码
// src/repositories/trans-message.repository.ts  
import { Injectable } from '@nestjs/common';  
import { InjectRepository } from '@nestjs/typeorm';  
import { Repository } from 'typeorm';  
import { TransMessage } from '../entities/trans-message.entity';  
 
@Injectable()  
export class TransMessageRepository {  
  constructor(  
    @InjectRepository(TransMessage)  
    private readonly repo: Repository<TransMessage>,  
  ) {}  
 
  // 插入消息  
  async insert(message: TransMessage): Promise<void> {  
    await this.repo.insert(message);  
  }  
 
  // 更新消息  
  async update(message: TransMessage): Promise<void> {  
    await this.repo.update(  
      { id: message.id, serviceName: message.serviceName },  
      message,  
    );  
  }  
 
  // 按主键查询单条消息  
  async findByIdAndService(id: string, serviceName: string): Promise<TransMessage | null> {  
    return this.repo.findOne({ where: { id, serviceName } });  
  }  
 
  // 按类型和服务名查询消息列表  
  async findByTypeAndService(type: MessageType, serviceName: string): Promise<TransMessage[]> {  
    return this.repo.find({ where: { type, serviceName } });  
  }  
 
  // 按主键删除消息  联合主键
  async deleteByIdAndService(id: string, serviceName: string): Promise<void> {  
    await this.repo.delete({ id, serviceName });  
  }  
}  

关键点:

  • 方法覆盖:完整实现增删改查(insert/update/find/delete)。
  • 联合主键处理:updatedelete使用{ id, serviceName }条件对象。
  • 依赖注入:@InjectRepository集成TypeORM。

4 ) 定时任务(消息重发)

使用@nestjs/schedule实现周期性重发逻辑:

typescript 复制代码
// src/tasks/resend.task.ts  
import { Injectable, Logger } from '@nestjs/common';  
import { Cron, CronExpression } from '@nestjs/schedule';  
import { TransMessageRepository } from '../repositories/trans-message.repository';  
import { MessageType } from '../enums/message-type.enum';  
 
@Injectable()  
export class ResendTask {  
  private readonly logger = new Logger(ResendTask.name);  
 
  constructor(private readonly messageRepo: TransMessageRepository) {}  
 
  @Cron(CronExpression.EVERY_30_SECONDS) // 每30秒执行一次  
  async resendMessages() {  
    this.logger.log('触发消息重发任务');  
    const pendingMessages = await this.messageRepo.findByTypeAndService(  
      MessageType.SEND,  
      'order-service', // 示例服务名  
    );  
 
    for (const message of pendingMessages) {  
      // 重发逻辑(如调用RabbitMQ生产者)  
      this.logger.debug(`重发消息: ID=${message.id}, 序列=${message.sequence}`);  
      // 更新序列号  
      message.sequence += 1;  
      await this.messageRepo.update(message);  
    }  
  }  
}  

关键点:

  • 定时配置:@Cron(CronExpression.EVERY_30_SECONDS)支持灵活调度。
  • 业务解耦:任务类通过Repository操作数据库,与业务逻辑分离。
  • 日志跟踪:内置Logger记录任务执行状态。

或参考下面

使用 @nestjs/schedule 实现周期性消息重发:

typescript 复制代码
import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { TransMessageRepository } from './trans-message.repository';
import { TransMessageType } from './enums/trans-message-type.enum';
 
@Injectable()
export class MessageResendTask {
  private readonly logger = new Logger(MessageResendTask.name);
 
  constructor(
    private readonly transRepo: TransMessageRepository,
    private scheduler: SchedulerRegistry,
  ) {}
 
  // 启动定时任务(频率从配置读取)
  startResendTask(frequency: number) {
    const interval = setInterval(() => this.resendMessages(), frequency);
    this.scheduler.addInterval('resend-task', interval);
  }
 
  // 重发逻辑:获取待重发消息并处理 
  private async resendMessages(): Promise<void> {
    this.logger.log('Triggering message resend task...');
    const pendingMessages = await this.transRepo.findByTypeAndService(
      TransMessageType.SEND,
      'your-service-name',
    );
    
    for (const message of pendingMessages) {
      await this.handleResend(message);
      this.logger.debug(`Resent message ${message.id}`);
    }
  }
 
  private async handleResend(message: TransMessage): Promise<void> {
    // 实际重发逻辑(调用 RabbitMQ 生产者)
    // 更新 sequence 计数 
    await this.transRepo.update(message.id, message.service, {
      sequence: message.sequence + 1,
    });
  }
}

核心逻辑:周期性扫描待重发消息(SEND类型),调用RabbitMQ重发。

typescript 复制代码
// task/message-resend.task.ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { TransMessageRepository } from '../repository/trans-message.repository';
import { TransMessageType } from '../enum/trans-message-type.enum';
import { RabbitMQService } from '../service/rabbitmq.service';
 
@Injectable()
export class MessageResendTask {
  private readonly logger = new Logger(MessageResendTask.name);
 
  constructor(
    private readonly messageRepo: TransMessageRepository,
    private readonly rabbitService: RabbitMQService,
  ) {}
 
  @Cron(CronExpression.EVERY_30_SECONDS) // 每30秒执行 
  async resendMessages() {
    this.logger.log('消息重发任务启动');
    const pendingMessages = await this.messageRepo.findByTypeAndService(
      TransMessageType.SEND,
      'order-service', // 示例服务名
    );
 
    for (const message of pendingMessages) {
      try {
        // 重发消息到RabbitMQ
        await this.rabbitService.publish(
          message.exchange,
          message.routingKey,
          message.payload,
        );
        this.logger.debug(`消息重发成功: ${message.id}`);
      } catch (error) {
        this.logger.error(`消息重发失败: ${message.id}`, error.stack);
      }
    }
  }
}

工程示例:1

1 ) RabbitMQ配置与连接

安装依赖并配置模块:

bash 复制代码
npm install amqplib amqp-connection-manager # RabbitMQ客户端  
typescript 复制代码
// src/rabbitmq/rabbitmq.module.ts  
import { Module } from '@nestjs/common';  
import * as amqp from 'amqp-connection-manager';  
 
@Module({  
  providers: [  
    {  
      provide: 'RABBITMQ_CONNECTION',  
      useFactory: async () => {  
        return amqp.connect(['amqp://localhost']);  
      },  
    },  
  ],  
  exports: ['RABBITMQ_CONNECTION'],  
})  
export class RabbitMQModule {}  

2 ) 生产者服务(发送消息)

封装消息发送逻辑,并记录到数据库:

typescript 复制代码
// src/services/message-producer.service.ts  
import { Injectable } from '@nestjs/common';  
import { Inject } from '@nestjs/common';  
import { TransMessageRepository } from '../repositories/trans-message.repository';  
import { MessageType } from '../enums/message-type.enum';  
import * as amqp from 'amqp-connection-manager';  
 
@Injectable()  
export class MessageProducerService {  
  constructor(  
    private readonly messageRepo: TransMessageRepository,  
    @Inject('RABBITMQ_CONNECTION') private readonly connection: amqp.AmqpConnectionManager,  
  ) {}  
 
  async sendMessage(  
    exchange: string,  
    routingKey: string,  
    payload: object,  
    serviceName: string,  
  ): Promise<void> {  
    const channelWrapper = this.connection.createChannel({  
      json: true,  
      setup: (channel) => channel.assertExchange(exchange, 'topic', { durable: true }),  
    });  
 
    const messageId = uuidv4(); // 生成UUID  
    const message = new TransMessage();  
    message.id = messageId;  
    message.serviceName = serviceName;  
    message.type = MessageType.SEND;  
    message.exchange = exchange;  
    message.routingKey = routingKey;  
    message.payload = JSON.stringify(payload);  
 
    // 先持久化到数据库  
    await this.messageRepo.insert(message);  
 
    // 发送到RabbitMQ  
    await channelWrapper.publish(exchange, routingKey, payload, { persistent: true });  
  }  
}  

3 ) 消费者服务(死信处理)

处理失败消息并转入死信队列:

typescript 复制代码
// src/services/message-consumer.service.ts  
import { Injectable } from '@nestjs/common';  
import { TransMessageRepository } from '../repositories/trans-message.repository';  
 
@Injectable()  
export class MessageConsumerService {  
  constructor(private readonly messageRepo: TransMessageRepository) {}  
 
  async handleDeadLetter(message: any): Promise<void> {  
    const deadMessage = new TransMessage();  
    deadMessage.id = message.properties.messageId;  
    deadMessage.serviceName = 'dead-letter-service';  
    deadMessage.type = MessageType.DEAD;  
    deadMessage.payload = JSON.stringify(message.content);  
    // 其他字段从原始消息解析...  
    await this.messageRepo.insert(deadMessage);  
  }  
}  

4 ) RabbitMQ命令示例

关键运维命令:

bash 复制代码
创建交换机  
rabbitmqadmin declare exchange name=order_exchange type=topic durable=true  
 
创建队列与死信配置  
rabbitmqadmin declare queue name=order_queue durable=true arguments='{"x-dead-letter-exchange":"dead_letter_exchange"}'  
 
绑定队列到交换机  
rabbitmqadmin declare binding source=order_exchange destination=order_queue routing_key=order.*  

工程示例:2

1 ) 方案 1:基础消息发送与持久化

typescript 复制代码
// 生产者服务
import { Injectable } from '@nestjs/common';
import { TransMessageRepository } from './trans-message.repository';
import { RabbitMQService } from './rabbitmq.service';
 
@Injectable()
export class MessageProducer {
  constructor(
    private readonly transRepo: TransMessageRepository,
    private readonly rabbitService: RabbitMQService,
  ) {}
 
  async sendMessage(exchange: string, routingKey: string, payload: object): Promise<void> {
    const messageId = uuidv4();
    const messageEntity = this.transRepo.create({
      id: messageId,
      service: 'order-service',
      type: TransMessageType.SEND,
      exchange,
      routingKey,
      queue: 'orders',
      sequence: 0,
      payload: JSON.stringify(payload),
      date: new Date(),
    });
 
    // 先持久化到数据库
    await this.transRepo.insert(messageEntity);
 
    // 再发送到 RabbitMQ
    await this.rabbitService.publish(exchange, routingKey, payload);
  }
}

2 ) 方案 2:死信队列处理失败消息

RabbitMQ 配置命令:

bash 复制代码
创建死信交换机和队列 
rabbitmqadmin declare exchange name=dlx type=direct 
rabbitmqadmin declare queue name=dead_letters 
rabbitmqadmin declare binding source=dlx destination=dead_letters routing_key=dead 

NestJS 消费者实现:

typescript 复制代码
// 消费者服务(带死信处理)
@Injectable()
export class OrderConsumer {
  constructor(
    private readonly transRepo: TransMessageRepository,
    private readonly rabbitService: RabbitMQService,
  ) {
    this.rabbitService.subscribe('orders', async (msg) => {
      try {
        await this.processOrder(msg);
        this.rabbitService.ack(msg);
      } catch (error) {
        // 标记为死信并重定向 
        await this.transRepo.update(msg.id, 'order-service', { 
          type: TransMessageType.DEAD_LETTER 
        });
        this.rabbitService.nack(msg, false, false); // 不重试,进入死信队列
      }
    });
  }
 
  private async processOrder(msg: any): Promise<void> {
    // 业务处理逻辑
  }
}

3 ) 方案 3:事务性发件箱模式

typescript 复制代码
// 事务管理器(数据库与 MQ 一致性)
import { UnitOfWork } from '@nestjs/uow';
 
@Injectable()
export class TransactionalOutbox {
  constructor(
    private readonly uow: UnitOfWork,
    private readonly rabbitService: RabbitMQService,
    private readonly transRepo: TransMessageRepository,
  ) {}
 
  @Transactional()
  async publishWithTransaction(
    exchange: string,
    routingKey: string,
    payload: object,
  ): Promise<void> {
    const messageId = uuidv4();
    await this.transRepo.insert({
      id: messageId,
      service: 'payment-service',
      type: TransMessageType.SEND,
      // ...其他字段
    });
 
    // 在事务提交后发送消息
    this.uow.onCommit(async () => {
      await this.rabbitService.publish(exchange, routingKey, payload);
    });
  }
}

工程示例:3

1 ) 方案1:基础消息持久化

typescript 复制代码
// service/rabbitmq.service.ts(基础版)
import { Injectable } from '@nestjs/common';
import * as amqp from 'amqplib';
 
@Injectable()
export class RabbitMQService {
  private connection: amqp.Connection;
  private channel: amqp.Channel;
 
  async connect(url: string): Promise<void> {
    this.connection = await amqp.connect(url);
    this.channel = await this.connection.createChannel();
  }
 
  async publish(exchange: string, routingKey: string, payload: string): Promise<boolean> {
    return this.channel.publish(exchange, routingKey, Buffer.from(payload), {
      persistent: true, // 消息持久化
    });
  }
}

2 ) 方案2:事务性消息保障

typescript 复制代码
// 在RabbitMQService中添加事务支持
async publishTransactional(
  exchange: string,
  routingKey: string,
  payload: string,
  dbTransaction: EntityManager, // TypeORM事务对象 
): Promise<boolean> {
  await this.channel.assertExchange(exchange, 'direct', { durable: true });
  try {
    // 1. 数据库保存消息
    const message = new TransMessage();
    // ...填充字段
    await dbTransaction.save(TransMessage, message);
 
    // 2. RabbitMQ事务发送
    await this.channel.txSelect();
    this.channel.publish(exchange, routingKey, Buffer.from(payload), { persistent: true });
    await this.channel.txCommit();
 
    return true;
  } catch (error) {
    await this.channel.txRollback();
    throw error;
  }
}

3 ) 方案3:死信队列(DLX)处理

RabbitMQ命令配置:

bash 复制代码
创建死信交换机和队列
rabbitmqadmin declare exchange name=dlx type=direct
rabbitmqadmin declare queue name=dead_messages
rabbitmqadmin declare binding source=dlx routing_key=dead destination=dead_messages
 
主队列绑定死信参数 
rabbitmqadmin declare queue name=order_queue arguments='{"x-dead-letter-exchange":"dlx", "x-dead-letter-routing-key":"dead"}'
typescript 复制代码
// NestJS死信处理器
@Injectable()
export class DeadLetterConsumer {
  @RabbitSubscribe({
    exchange: 'dlx',
    routingKey: 'dead',
    queue: 'dead_messages',
  })
  async handleDeadMessage(message: any): Promise<void> {
    // 1. 保存死信到数据库
    const deadMsg = new TransMessage();
    deadMsg.type = TransMessageType.DEAD;
    // ...填充其他字段
    await this.messageRepo.insertMessage(deadMsg);
 
    // 2. 告警通知
    this.alertService.notify(`死信消息: ${message.content}`);
  }
}

RabbitMQ 周边配置详解

1 ) 连接配置(rabbitmq.module.ts):

typescript 复制代码
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
 
@Module({
  imports: [
    RabbitMQModule.forRoot(RabbitMQModule, {
      exchanges: [
        { name: 'orders', type: 'direct' },
        { name: 'dlx', type: 'direct' },
      ],
      uri: 'amqp://user:password@localhost:5672',
      connectionInitOptions: { wait: true },
    }),
  ],
})
export class RabbitConfigModule {}

2 ) 消息序列化与重试:

typescript 复制代码
// 消息格式化中间件
@Injectable()
export class RabbitFormatInterceptor implements RabbitHandlerConfig {
  async handle(message: any): Promise<any> {
    try {
      return JSON.parse(message.content.toString());
    } catch (e) {
      throw new Error('Invalid message format');
    }
  }
}

3 ) 监控与告警:

  • 使用 amqplibchannel.assertQueue() 监测队列积压
  • 集成 Prometheus 统计消息吞吐量

关键配置与知识点补充

1 ) TypeORM模块配置(app.module.ts)

typescript 复制代码
import { TransMessage } from './entity/trans-message.entity';
 
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'msg_db',
      entities: [TransMessage],
      synchronize: true,
    }),
    TypeOrmModule.forFeature([TransMessage]),
  ],
  providers: [RabbitMQService, MessageResendTask],
})
export class AppModule {}

2 ) RabbitMQ连接优化

typescript 复制代码
// 使用amqp-connection-manager增强稳定性
import * as amqp from 'amqp-connection-manager';
 
async createConnection(url: string) {
  const connection = amqp.connect([url]);
  connection.on('connect', () => logger.log('RabbitMQ连接成功'));
  connection.on('disconnect', (err) => logger.error('RabbitMQ断开', err));
  this.channel = await connection.createChannel({
    json: true,
    setup: (ch) => ch.assertExchange('orders', 'direct', { durable: true }),
  });
}

3 ) 消息序列化规范

typescript 复制代码
// 统一消息格式 
interface BusinessMessage {
  event: string; // 事件类型 业务数据
  timestamp: number;
}
 
// 发送时序列化
async publishEvent) {
  const payload: BusinessMessage = {
    event,
    data,
    timestamp: Date.now(),
  };
  await this.rabbitService.publish('events', 'order.created', JSON.stringify(payload));
}

优化方向

  1. 可靠性保障:
    • 消息持久化:数据库+RabbitMQ双写确保消息不丢失。
    • 重试机制:定时任务+序列号控制实现指数退避重发。
  2. 扩展性设计:
    • 模块化拆分:实体、存储库、任务独立封装,支持微服务扩展。
    • 配置中心:重试频率通过环境变量动态注入(如RESEND_INTERVAL)。
  3. 监控增强:
    • 日志追踪:记录消息生命周期(发送→重发→死信)。
    • 指标上报:集成Prometheus统计消息堆积量。

关键提示:

  • 死信队列(DLX):当消息多次重发失败后,RabbitMQ自动将其路由到指定死信交换机。
  • 幂等性设计:消费者需通过messageId去重,避免重复处理。

通过以上方案,实现了分布式事务消息的可靠投递、状态跟踪与失败恢复,为系统提供了强一致性保障。

关键设计总结

1 ) 可靠性保障:

  • 所有消息先持久化到数据库,再发送至 RabbitMQ
  • sequence 计数器跟踪重发次数,避免无限循环
  • 消息持久化双写:
    • 数据库:存储消息状态(联合主键id+service
    • RabbitMQ:启用persistent: true+队列持久化(durable: true)

2 ) 事务一致性:

  • 发件箱模式(Transactional Outbox)确保数据库与 MQ 状态同步
  • 本地事务:消息入库与业务操作在同一数据库事务中
  • 最终一致性:重发机制保证消息最终投递

3 ) 死信管理:

  • 自动将处理失败的消息路由至死信队列,隔离故障。
  • 利用RabbitMQ的x-dead-letter-exchange自动转移异常消息
  • 死信消息标记为DEAD类型并触发告警

4 ) 重发机制核心:

  • 定时任务扫描SEND状态消息
  • 指数退避重试:通过sequence字段控制重试间隔

5 ) 扩展性优化:

  • 定时任务频率动态可调(通过环境变量配置)。
  • 支持水平扩展多个消费者实例。

关键提示:

  • 联合主键设计:避免跨服务ID冲突,通过id(UUID)+service唯一标识消息
  • 序列化选择:建议JSON序列化,避免Protobuf等跨语言兼容问题
  • 队列声明幂等性:在NestJS启动时声明交换机/队列(assertExchange/assertQueue

初学者提示:

  • 死信队列(DLQ):当消息多次重试失败后,会被转移到特殊队列用于人工干预。
  • TypeORM 实体:将数据库表结构映射为 TypeScript 类,简化数据库操作。
  • Publisher Confirm:RabbitMQ 的可靠性机制,生产者确认消息已持久化到 Broker。

此设计完整实现了分布式事务中的可靠消息模式,通过数据库与 RabbitMQ 的协同,解决了服务间数据一致性问题,同时提供三种工程方案适应不同场景需求

相关推荐
搬砖的kk2 小时前
Flutter适配鸿蒙:跨平台力量为鸿蒙生态注入增长新动能
分布式·flutter·harmonyos
Wang's Blog2 小时前
Kafka: 动态配置刷新与分布式配置管理深度实践
分布式·kafka
程序员miki3 小时前
Redis核心命令以及技术方案参考文档(分布式锁,缓存业务逻辑)
redis·分布式·python·缓存
北城以北88883 小时前
RabbitMQ基础知识
spring boot·分布式·rabbitmq·intellij-idea
是阿威啊3 小时前
【第三站】本地虚拟机部署hive集群
linux·数据仓库·hive·hadoop·分布式
云技纵横3 小时前
本地限流与 Redis 分布式限流的无缝切换 技术栈:Sentinel 线程池隔离 + Nginx + Kafka
redis·分布式·sentinel
赵榕3 小时前
RabbitMQ发布订阅模式多实例消费者防止重复消费实现方式
微服务·消息队列·rabbitmq
源代码•宸3 小时前
goframe框架签到系统项目开发(分布式 ID 生成器、雪花算法、抽离业务逻辑到service层)
经验分享·分布式·mysql·算法·golang·雪花算法·goframe
初级炼丹师(爱说实话版)4 小时前
ROS分布式通信和Socket.io通信的区别
分布式