分布式事务-MQ事务消息学习与落地方案

本文参考:

Apache RocketMQ 事务消息

基础概念

分布式事务解决的核心问题:降低分布式环境下如网络超时、系统宕机带来的不良影响,保证分布式系统中不同应用程序的数据一致性(重点关注事务 ACID 特性中的 A 原子性和 C 一致性,因为 I 隔离性在分布式环境中天然存在,持久化 D取决于业务是否需要将结果落库了)。

MQ 事务消息实现分布式事务「最终一致性」的核心:基于 half 消息 + commit/rollback 消息的两阶段提交,配合回调反查、MQ 重试机制,实现本地事务和下游消息发送的一致性。

看起来概念是有点复杂,想想看为啥别的方案就不行呢?主要还是有几个问题

  1. MQ 不可靠,可能会丢消息,而且我们会被 MQ 超时影响
  2. 发 MQ 消息和本地事务没法保证一致性
  • 本地事务和发 MQ 消息放一块
    本地事务发送成功了,机器宕机,MQ 发消息执行失败了,两者不一致

  • 把 MQ 发送放到本地事务中

    MQ 发送超时失败了,但有可能 MQ 是由于网络延迟,其实是发送到消费者且执行成功了。但本地事务捕获到异常直接回滚了,两者不一致

基本原理

在这里插入图片描述

在这里插入图片描述

拆解下来,主要有以下4个步骤:

  1. 发送半消息:上游向 RocketMQ Broker 发送一条半消息,该消息在 Broker 端的事务消息日志中被标记为 “prepared” 状态
  2. 执行本地事务:RocketMQ 会通知上游执行本地事务。如果本地事务执行成功,发送方通知 RocketMQ Broker 提交该事务消息
  3. 提交事务消息:RocketMQ 收到该提交消息以后,会讲该消息的状态从“prepared” 改为 “committed”,并使该消息可以被消费者消费
  4. 回滚事务消息:如果本地事务执行失败,应用程序通知 RocketMQ Broker 回滚该事务消息,RocketMQ 将该消息状态从 “prepared” 标记为 “rollback”,并将该消息从事务消息日志中删除,从而保证该消息不会被消费者消费。

在正常情况下,一切顺利,那么就是本地事务和发送消息都成功,下游也把消息消费成功。

我们看下非正常情况下的几种表现:

  1. 第一步半消息发送失败

    不用执行本地事务,上游发送者直接会感知到发送失败,可以由上游决定是否需要重试

  2. 本地事务执行失败

    直接发送 rollback 给 MQ broker,下游也收不到消息无需执行

  3. 第二个半消息发送失败
    MQ 会在第 5 步发送回查消息,拿到上游回查的结果,第 7 步类似第 4 步,根据回查结果 commit/rollback 决定半消息是否提交或回滚

  4. 消息接收方消费失败
    依赖 MQ 重投机制不断重试,也需要注意幂等实现

具体实现

首先 MQ 版本得支持事务消息,应该 RocketMQ 4.3.0正式引入了事务消息,上游发送者能感知到就行了。

假设上游业务是订单服务,下游业务是库存服务,我们想实现订单关单、库存回滚的分布式事务。我们重点是需要去实现 org.apache.rocketmq.client.producer.TransactionListener 这个接口,假设在 spring cloud 环境内:

  1. 在配置环境中,标明 producer 是事务的,并且 OrderCloseTransactionListener 实现了 TransactionListener 接口

在这里插入图片描述

  1. 实现 TransactionListener 的核心方法,executeLocalTransaction 是执行本地事务的方法,checkLocalTransaction 是用于 MQ 检查本地事务状态的方法

在这里插入图片描述

虽然上面 OrderCloseTransactionListener 看起来是通过 MQ 消费者的形式去配置的,但其实在发送者发送的时候,half 消息 + OrderCloseTransactionListener + commit/rollback 三步都是同步执行的。所以应该只是 spring cloud 接入写法的问题,这样子方便去拓展。

  1. 实现消费者去监听上述发送方消息

总结思考

这里主要是一些延伸思考,看下 MQ 事务消息的限制,以及它与其他分布式事务解决方案,比方说本地消息表、TCC 的对比和选型建议

使用限制

  1. 消息类型一致性

    事务消息仅支持在 MessageType 为 Transaction 的主题内使用,即事务消息只能发送至类型为事务消息的主题中,发送的消息的类型必须和主题的类型一致。

  2. 消费事务性
    事务消息仅保证上游本地事务和发送消息给下游两者的一致性,并不保证下游消费结果和上游事务的一致性。因此需要下游业务自行保障,有短暂的消费失败,也可以通过消息重试来达到最终处理成功,但需要做好幂等

  3. 中间状态可见性

    MQ 事务消息为最终一致性,即在发送者消息提交到下游消费端处理完成前,下游分支和上游事务之间可能会存在不一致,因此事务消息仅适合接受异步执行短暂不一致的事务场景。需要业务去合理处理中间状态,有些中间态不需要对用户披露,后端不处理也行,有些会对用户表达,那就需要设计一下。

  4. 事务超时机制

    事务消息的生命周期存在超时机制,即半事务消息被生产者发送服务端后,如果在指定时间内服务端无法确认提交或者回滚状态,则消息默认会被回滚。事务超时时间,可以参考 Apache RocketMQ 参数限制

选型思考建议

  1. 和本地消息表一样,都依赖 MQ 消息重投来保障最终一致性,非强一致性

  2. 实现成本上
    MQ 事务消息首先需要 MQ 支持事务消息,需要实现 TransactionListener 接口,业务侵入性不低。
    本地消息表不需要依赖 MQ 版本,对业务侵入性没那么高,但是也需要单独创建一张新表,由定时任务去轮询投递,改造成本也不低。

  3. MQ 事务消息不像 TCC 一样,需要去关心业务回滚逻辑,回滚逻辑一般在长链路里都是蛮复杂的,并且 TCC 需要推动下游去提供接口,相比于让他们直接消费消息,TCC 接口的业务侵入性明显更高。

  4. MQ 事务消息在消息可追踪性上可能不如本地消息表,它不会去额外存储消息。如果有一些消息持久化、对账的需求,可以使用本地消息表的方案,因为其会存储一遍待发送的消息,所以在消息回溯上会更好。

<think>嗯,用户这次想了解分布式事务一致性解决方案和原理,还特别提到要实现方法及最佳实践。看来用户是个有一定技术背景的工程师,可能在设计分布式系统时遇到了事务一致性的实际问题。用户给出的引用材料质量参差不齐,需要筛选有效信息。引用[2]和[3]比较有价值,提到了2PC、3PC强一致性方案和基于消息队列的最终一致性方案;引用[4]明显是广告可以忽略;引用[1]虽然提到RabbitMQ但不够具体。用户上次的问题是关于分布式事务一致性解决方案的,这次应该是在寻求更深入的技术细节。ta可能正在做技术选型,需要比较不同方案的优劣。从ta要求“实现方法及最佳实践”来看,更关注落地层面而非纯理论。注意到用户对广告内容很敏感(引用[4]),回答时应当保持专业性和中立性,避免任何营销倾向。用户似乎更信任具体的技术方案描述而非概念性内容,所以重点解析两种主流实现方式的技术细节比较合适。在技术深度上需要平衡:既不能太基础(用户显然知道CAP定理这些),也不能太晦涩(具体数学公式和算法细节可以放到延伸问题里)。最佳实践部分要突出可操作性,比如“幂等消费设计注意事项”这类具体建议。用户没有限定具体场景,但提到“订单和库存系统”(引用[1]),所以案例可以侧重电商交易场景。RabbitMQ和RocketMQ的实现差异也要说清楚,因为引用[3]专门提到RocketMQ事务消息特性。最后考虑设计延伸问题时,要覆盖用户可能遇到的后续问题:如何选择协议、常见误区、行业案例等。特别是性能优化这点,高并发场景下的事务处理是很多工程师的痛点。</think> 以下是分布式事务一致性实现方法及最佳实践的全面解析,结合理论基础工程实践: --- ### **一、一致性实现的核心方法** #### 1. **强一致性方案** - **两阶段提交协议(2PC)** - **流程**: ```mermaid graph LR A[协调者] -->|Prepare请求| B[参者1] A -->|Prepare请求| C[参者2] B -->|投票结果| A C -->|投票结果| A A -->|全局提交/回滚| B A -->|全局提交/回滚| C ``` - **问题**:同步阻塞(参者阻塞等待)、协调者单点故障。 - **适用场景**:数据库跨库事务(如MySQL XA)。 - **三阶段提交协议(3PC)** - **改进点**:增加预提交阶段(CanCommit)和超时机制,降低阻塞概率[^2]。 - **缺点**:网络分区时仍可能数据不一致。 #### 2. **最终一致性方案** - **基于消息队列的实现** ```mermaid sequenceDiagram 参者A->>MQ: 发送事务消息(Prepared状态) MQ-->>参者A: 消息存储成功,但未投递 参者A->>本地DB: 执行业务并提交 参者A->>MQ: 确认消息(Commit/Rollback) MQ->>参者B: 投递消息者B->>本地DB: 消费消息并提交操作 ``` - **核心保障**: - **事务消息**:RocketMQ支持(半消息机制)[^3] - **幂等消费**:通过唯一ID(如业务主键)避免重复处理 - **对账补偿**:定时核对数据差异,触发补偿任务 - **适用场景**:订单-库存系统、支付-通知链路[^1]。 --- ### **二、最佳实践关键设计** 1. **选择依据** | 场景需求 | 推荐方案 | 理由 | |------------------|-------------------|--------------------------| | 金融交易 | 2PC/TCC | 强一致性优先 | | 高并发容忍延迟 | 消息队列最终一致 | 系统解耦,吞吐量高 | 2. **关键设计原则** - **服务解耦**:业务操作消息发送分离(e.g., 先本地事务,再发消息- **幂等设计**:消费端通过`(消息ID + 业务键)`保证唯一性 - **事务表兜底**:本地事务表中记录消息状态,定时扫描补偿[^3] - **限流降级**:MQ消费者线程池隔离,避免雪崩 3. **典型架构示例(订单系统)** ```plaintext [订单服务] --事务消息--> [MQ] --异步投递--> [库存服务] ↓ | [本地事务表] [定时对账服务](核查库存扣减状态) ``` --- ### **三、常见问题解决方案** - **消息丢失**: - 生产者:本地事务表 + 定时重发 - MQ:RocketMQ事务消息机制(自动回查) - **消费失败**: - 死信队列 + 人工介入 - 指数退避重试策略(如1s/5s/30s) - **数据不一致**: - 对账系统定时比对核心表(如订单状态 vs 库存流水) --- ### **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

互联网民工蒋大钊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值