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 的协同,解决了服务间数据一致性问题,同时提供三种工程方案适应不同场景需求

相关推荐
子非衣1 小时前
CenOS7安装RabbitMQ(含延迟队列插件)
分布式·rabbitmq·ruby
linweidong2 小时前
中科曙光Java后端开发面试题及参考答案
分布式·设计模式·spring mvc·tcp协议·三次握手·后端开发·java面经
独自破碎E2 小时前
说说RabbitMQ的集群模式
rabbitmq
rustfs2 小时前
使用 RustFS和 Arq,打造 PC 数据安全备份之道
分布式·docker·云原生·rust·开源
后季暖3 小时前
kafka原理详解
分布式·kafka
回家路上绕了弯3 小时前
Seata分布式事务实战指南:从原理到微服务落地
分布式·后端
LDG_AGI3 小时前
【机器学习】深度学习推荐系统(二十六):X 推荐算法多模型融合机制详解
人工智能·分布式·深度学习·算法·机器学习·推荐算法
利刃大大4 小时前
【RabbitMQ】重试机制 && TTL && 死信队列
分布式·后端·消息队列·rabbitmq·队列
talle20214 小时前
Hadoop分布式资源管理框架【Yarn】
大数据·hadoop·分布式
LDG_AGI4 小时前
【机器学习】深度学习推荐系统(二十五): X 推荐算法特征系统详解:230+ 特征全解析
人工智能·分布式·深度学习·算法·机器学习·推荐算法