核心组件实现
1 ) 枚举定义(MessageType)
定义消息类型(发送、接收、死信):
// src/enums/message-type.enum.ts
export enum MessageType {
SEND = 'SEND', // 发送消息
RECEIVE = 'RECEIVE', // 接收消息
DEAD = 'DEAD' // 死信消息
}
此枚举映射数据库的 type 字段,确保消息状态可追溯
关键点:
- 严格匹配数据库字段:确保枚举值与数据库存储一致。
- 语义明确:避免缩写(如RECEIVE非RECV)。
2 ) 实体建模(TransMessage)
使用TypeORM定义消息实体,对应数据库表trans_message:
// 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修饰id和serviceName。 - 字段映射:
@Column({ name: 'routing_key' })解决数据库下划线命名问题。 - 默认值:
sequence初始为0,createdAt自动生成时间戳。
3 ) 存储层实现(Repository/DAO)
通过TypeORM Repository封装数据库操作:
// 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)。 - 联合主键处理:
update和delete使用{ id, serviceName }条件对象。 - 依赖注入:
@InjectRepository集成TypeORM。
4 ) 定时任务(消息重发)
使用@nestjs/schedule实现周期性重发逻辑:
// 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 实现周期性消息重发:
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重发。
// 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配置与连接
安装依赖并配置模块:
npm install amqplib amqp-connection-manager # RabbitMQ客户端
// 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 ) 生产者服务(发送消息)
封装消息发送逻辑,并记录到数据库:
// 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 ) 消费者服务(死信处理)
处理失败消息并转入死信队列:
// 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命令示例
关键运维命令:
创建交换机
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:基础消息发送与持久化
// 生产者服务
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 配置命令:
创建死信交换机和队列
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 消费者实现:
// 消费者服务(带死信处理)
@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:事务性发件箱模式
// 事务管理器(数据库与 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:基础消息持久化
// 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:事务性消息保障
// 在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命令配置:
创建死信交换机和队列
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"}'
// 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):
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 ) 消息序列化与重试:
// 消息格式化中间件
@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 ) 监控与告警:
- 使用
amqplib的channel.assertQueue()监测队列积压 - 集成 Prometheus 统计消息吞吐量
关键配置与知识点补充
1 ) TypeORM模块配置(app.module.ts)
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连接优化
// 使用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 ) 消息序列化规范
// 统一消息格式
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));
}
优化方向
- 可靠性保障:
- 消息持久化:数据库+RabbitMQ双写确保消息不丢失。
- 重试机制:定时任务+序列号控制实现指数退避重发。
- 扩展性设计:
- 模块化拆分:实体、存储库、任务独立封装,支持微服务扩展。
- 配置中心:重试频率通过环境变量动态注入(如
RESEND_INTERVAL)。
- 监控增强:
- 日志追踪:记录消息生命周期(发送→重发→死信)。
- 指标上报:集成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 的协同,解决了服务间数据一致性问题,同时提供三种工程方案适应不同场景需求
908

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



