一、基于MQ的分布式事务模式
1. 本地消息表(Local Transaction Table)
适用场景: 需要强最终一致性的场景(如订单创建后发送通知)
实现步骤:
-
业务与消息原子化写入
- 业务操作和消息日志(本地消息表)在同一个数据库事务中完成。
- 消息表字段 :
msg_id
,status
(待发送/已发送),topic
,payload
,retry_count
,create_time
。
sql-- 示例表结构 CREATE TABLE local_message ( msg_id VARCHAR(64) PRIMARY KEY, status TINYINT NOT NULL COMMENT '0-待发送,1-已发送', topic VARCHAR(255) NOT NULL, payload TEXT NOT NULL, retry_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );
-
后台任务异步发送消息
- 定时扫描本地消息表中
status=0
的记录,发送到MQ。 - 发送成功后更新状态为
1
,失败则重试(需设置最大重试次数)。
- 定时扫描本地消息表中
-
消费者幂等处理
- 消费者需保证多次处理同一消息的结果一致(通过唯一ID去重)。
优点 : 强一致性,实现简单
缺点: 需要维护本地消息表,数据库压力较大
2. MQ事务消息(如RocketMQ)
适用场景: 需要简化本地消息表维护的场景
实现步骤:
-
生产者发送半消息(Half Message)
- 发送消息到MQ,但消息对消费者不可见(处于
PREPARED
状态)。
- 发送消息到MQ,但消息对消费者不可见(处于
-
执行本地事务
- 执行业务逻辑(如扣减库存),记录事务结果。
-
提交/回滚消息
- 本地事务成功:向MQ发送
commit
,消息变为COMMITTED
,消费者可见。 - 本地事务失败:向MQ发送
rollback
,消息被删除。
- 本地事务成功:向MQ发送
-
事务状态回查(Checkback)
- 若生产者未响应commit/rollback,MQ会回调生产者的接口确认事务状态。
RocketMQ事务消息代码示例:
java
// 生产者代码
TransactionMQProducer producer = new TransactionMQProducer("group");
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
// 执行本地事务(如更新数据库)
boolean success = orderService.createOrder(arg);
return success ? LocalTransactionState.COMMIT_MESSAGE :
LocalTransactionState.ROLLBACK_MESSAGE;
} catch (Exception e) {
return LocalTransactionState.UNKNOW;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 回查本地事务状态
return orderService.isOrderExists(msg.getKeys()) ?
LocalTransactionState.COMMIT_MESSAGE :
LocalTransactionState.ROLLBACK_MESSAGE;
}
});
producer.sendMessageInTransaction(msg, null);
优点 : 无侵入,不需要本地消息表
缺点: 依赖MQ的事务消息功能(仅部分MQ支持)
3. 最大努力通知(Best-Effort Delivery)
适用场景: 允许最终一致且对时效性要求不高的场景(如退款结果通知)
实现步骤:
-
业务操作与消息解耦
- 先完成本地事务,再异步发送消息(允许消息丢失)。
-
消息重试机制
- 设置阶梯式重试间隔(如1s, 5s, 10s),达到最大次数后告警人工处理。
-
对账补偿
- 定期扫描业务数据,对未通知成功的记录补发消息。
优点 : 实现简单,系统压力小
缺点: 一致性弱,需配合对账系统
二、关键问题与解决方案
1. 消息丢失问题
- 生产者端
- 开启MQ的
confirm
模式(如RabbitMQ)或事务消息(如RocketMQ)。
- 开启MQ的
- 消费者端
- 手动ACK机制,处理成功后再确认消息。
2. 消息重复消费
- 幂等性设计
- 业务层通过唯一ID(如订单号)判断是否已处理。
- 数据库唯一索引或Redis SetNX去重。
3. 消息顺序性
- 单一队列分区:同一业务ID的消息发送到同一个队列(如Kafka的Key Partitioning)。
- 消费者单线程处理同一分区的消息。
三、方案对比与选型
方案 | 一致性强度 | 实现复杂度 | 适用场景 |
---|---|---|---|
本地消息表 | 强最终一致 | 中 | 需要高可靠性的核心业务 |
MQ事务消息 | 强最终一致 | 低 | RocketMQ用户,希望简化实现 |
最大努力通知 | 弱最终一致 | 低 | 非核心业务(如通知类) |
四、最佳实践
-
消息表设计优化
- 分库分表:按业务ID哈希分片,避免单表过大。
- 索引优化:对
status
和create_time
建立联合索引。
-
监控与告警
- 监控MQ堆积量、消息延迟。
- 本地消息表中长期未处理的消息触发告警。
-
事务消息超时控制
- 设置事务超时时间(如RocketMQ的
transactionTimeout
),避免事务悬挂。
- 设置事务超时时间(如RocketMQ的
五、总结
实现分布式事务的核心是 将分布式事务拆分为本地事务 + 可靠消息传递。选择方案时需根据业务对一致性的要求权衡复杂度:
- 强一致性要求高 → 本地消息表或MQ事务消息
- 允许延迟最终一致 → 最大努力通知 + 对账补偿
结合幂等性设计和完善的重试机制,可构建高可靠的分布式事务系统。