分布式事务

131 阅读16分钟
方案​​一致性​​性能​​侵入性​​适用场景​​代表框架​
​2PC/3PC​强一致金融转账、跨库强一致Atomikos, MySQL XA
​TCC​最终一致高并发短事务(支付/库存)Seata, ByteTCC
​Saga​最终一致长流程业务(订单履约/旅行)Netflix Conductor, Seata
​本地消息表​最终一致异步数据同步、解耦场景自研+数据库
​事务消息​最终一致MQ支持的异步解耦(支付通知)RocketMQ, Kafka

⚙️ 一、​​强一致性方案​​(牺牲可用性,保证数据强一致)

​​2PC(两阶段提交)​​

​​原理​​:协调者分两阶段管理事务:

两阶段提交协议(Two-Phase Commit, 2PC)是分布式事务中保证原子性与一致性的核心协议,通过协调者与参与者的交互实现跨节点事务的“全成功”或“全失败”。以下是其原理的详细解析:

⚙️ 一、核心角色与设计目标

  1. 协调者(Coordinator)
    • 事务的全局管理者,负责发起事务、收集参与者响应并决策提交或回滚
  2. 参与者(Participant)
  • 实际执行本地事务的资源管理器(如数据库、消息队列),向协调者反馈本地事务状态
  1. 设计目标

解决分布式环境下多个独立资源的事务原子性问题(例如:跨行转账需同时扣款与到账)

🔄 二、两阶段执行流程

阶段1:准备阶段(Prepare Phase)

  1. 协调者操作
    • 向所有参与者发送 Prepare请求,携带全局事务ID(XID)
  2. 参与者操作
    • 执行本地事务 (如执行SQL更新),但 不提交
    • 记录Undo/Redo日志 :用于回滚或恢复(如MySQL的redo log标记为prepare状态)。
    • 锁定资源 :防止其他事务修改(如锁定账户余额)
    • 反馈结果:成功则返回 Yes,失败或超时则返回 No

阶段2:提交阶段(Commit Phase)

  1. 协调者决策 ****:
    • 全部参与者返回 Yes :发送 Commit指令。
    • 任一参与者返回 No****或超时 ****:发送 Rollback指令
  2. 参与者执行 ****:
    • 收到 Commit:提交本地事务,释放锁,返回确认(ACK)。
    • 收到 Rollback:利用Undo日志回滚,释放锁

​​优点​​:强一致性,实现简单(如数据库支持XA协议)。

​​缺点​​:

1、同步阻塞(性能问题)

资源长期锁定​​:在准备阶段(Prepare Phase),所有参与者需锁定本地资源并等待协调者指令。在此期间,其他事务无法访问被锁资源,导致系统吞吐量急剧下降

​全局等待​​:协调者需收集所有参与者的响应后才能进入下一阶段。任一参与者的响应延迟或阻塞,会导致整个事务链停滞,类似“全队等一人”的阻塞场景 ​​并发性能差​​:高并发场景下,大量事务因等待资源锁而排队,系统响应时间显著增加,难以支撑高吞吐需求

2、单点故障(协调者宕机导致事务挂起)

​协调者宕机导致事务悬挂​​:若协调者在第二阶段(Commit Phase)崩溃,参与者将永久阻塞于“已准备但未决”状态,资源锁无法释放,系统可能陷入死锁。 需要重新选去事务协调者。

3、数据不一致

​网络分区引发分裂​​:协调者发出提交指令后,若网络故障导致部分参与者未收到COMMIT,已提交的参与者与未提交者之间会产生数据分歧。

​​场景​​:

数据库集群分库,多个数据库数据一致性,对一致性要求极高的金融核心业务。

​​3PC(三阶段提交)​​

  1. ​​CanCommit 阶段(询问阶段)​​

​​协调者动作​​:向所有参与者发送事务询问请求(CanCommit),询问是否具备执行条件(如资源可用性、锁能否获取)。

​​参与者动作​​:

检查自身状态(如资源、锁、业务规则),​​不执行事务操作​​,仅做可行性预判。

若通过检查,返回 Yes并进入预备状态;否则返回 No。

​​特点​​:此阶段​​不锁定资源​​,轻量级操作,避免无效资源占用。

  1. ​​PreCommit 阶段(预提交阶段)​​

​​协调者决策​​:

​​全部返回 Yes​​:发送 PreCommit指令,进入预提交状态。

​​任一返回 No或超时​​:发送 Abort指令中止事务。

​​参与者动作​​:

收到 PreCommit后:

​​执行事务操作​​(如更新数据),但不提交。

​​记录 undo/redo 日志​​,用于故障恢复。

​​锁定资源​​,返回 ACK响应。

收到 Abort或超时未响应:立即中止事务并释放资源。

  1. ​​DoCommit 阶段(提交阶段)​​

​​协调者决策​​:

​​收到所有 ACK​​:发送 DoCommit指令。

​​未收到全部 ACK或超时​​:发送 Abort指令。

​​参与者动作​​:

收到 DoCommit:提交事务,释放资源锁,返回 ACK。

收到 Abort或​​超时未收到指令​​:

​​利用 undo 日志回滚​​事务。

​​超时自动提交​​:因 PreCommit 已达成共识,参与者默认提交(关键改进点)

  1. ​​超时自动决策机制​​

​​参与者超时行为​​:

​​CanCommit 超时​​ → 自动中止。

​​PreCommit 超时​​ → 自动中止。

​​DoCommit 超时​​ → ​​自动提交​​(因 PreCommit 已确认全局可提交)。

​​作用​​:避免因协调者故障导致参与者无限阻塞,显著减少死锁风险。

  1. ​​预提交状态(PreCommit)的引入​​

​​意义​​:

在 DoCommit 前明确所有参与者已就绪(通过 PreCommit 确认),​​降低“不确定状态”持续时间​​。

参与者记录 undo/redo 日志,确保故障后可恢复至一致状态。

  1. ​​非阻塞设计​​

协调者故障时,参与者可通过超时机制自主决策(提交或回滚),而无需等待协调者恢复,提升系统可用性

优点:

  1. 降低阻塞风险 ****:超时机制和自主决策避免资源长期锁定。
  2. 增强容错性 ****:协调者故障时参与者可继续推进事务。

缺点: 额外网络开销:三阶段通信比 2PC 多一轮交互,延迟更高 若 DoCommit 阶段网络分区,部分参与者超时提交,另一部分收到 Abort 回滚,导致数据分裂

使用场景: 弱强一致性,网络不稳定,超过1秒的事务。

分阶段事务的缺点

致命缺点

也是比较致命的缺点,强依赖数据库事务,XA协议。

二、​​最终一致性方案​​(保证可用性,容忍短暂不一致)

TCC(Try-Confirm-Cancel)是一种基于业务补偿的分布式事务解决方案,通过将事务拆分为 Try(尝试)、Confirm(确认)、Cancel(取消) 三个阶段实现最终一致性。以下是其核心原理及关键设计要点:

⚙️ **一、TCC 的核心三阶段

  1. Try (尝试阶段)
    • 目标 ****:预留资源并完成业务检查,不执行实际数据修改。
    • 操作 ****:
      • 冻结资源(如库存冻结、账户资金预扣)
      • 生成预记录(如创建状态为"待确认"的订单)
    • 特性 ****:
      • 幂等性 ****:支持重复调用(如网络重试)
      • 隔离性 ****:通过资源预留防止脏读(如冻结库存后,其他事务无法占用)
  2. Confirm (确认阶段)
    • 触发条件 ****:Try阶段所有参与者均成功。
    • 操作 ****:
      • 使用Try阶段预留的资源执行业务(如扣减冻结库存、确认订单生效)
    • 特性 ****:
      • 幂等性 ****:避免重复提交(如通过事务ID标记状态)。
      • 无业务检查 ****:Try阶段已确保资源可用性,Confirm只需提交。
  3. Cancel (取消阶段)
    • 触发条件 ****:Try阶段任一参与者失败或超时。
    • 操作 ****:
      • 释放预留资源(如解冻库存、删除预订单)
    • 特性 ****:
      • 幂等性 **:支持重复回滚
      • 空回滚处理 ****:Try未执行时,Cancel需跳过操作(通过日志检查)

问题

  1. 空回滚( Empty Rollback
    • 场景 ****:Try未执行(如网络超时),但协调者触发Cancel
    • 解决 ****:记录Try阶段日志,Cancel时检查无日志则跳过操作
  2. 防悬挂( Anti-Suspension
    • 场景 ****:Cancel先执行成功,Try后到达导致资源悬挂
    • 解决 ****:Cancel后记录状态,Try执行前检查是否已被取消
  3. 幂等控制
    • 方法 ****:为每个事务分配唯一ID,记录操作状态,重复请求直接返回历史结果

TCC 的核心优点

⏱️ *高性能与低资源阻塞

短时资源锁定

Try 阶段仅 预留资源(如冻结库存、预扣资金),不直接修改业务数据,资源锁定时间极短,显著减少并发冲突

无全局锁机制

TCC 通过业务层逻辑实现资源隔离(如预扣表、版本号控制),而非依赖数据库锁。这种设计允许不同事务在不同资源节点上并发执行,显著提升吞吐量

异步提交能力

Confirm/Cancel 阶段可异步执行,避免同步阻塞,支持高吞吐场景(如电商秒杀)。不像2pc和3pc那样强依赖数据库。做数据库资源占用,所以异步问题不大

🛡️ 高容错性与最终一致性*

  • 幂等性保障 ****:每个阶段支持重试,通过事务 ID 和状态日志确保重复操作不产生副作用
  • 故障恢复机制 ****:即使 Confirm/Cancel 阶段失败,可通过日志重试或人工干预恢复数据一致性

TCC 的核心缺点

  1. 💻 开发成本高 & 业务侵入性强
    • 三阶段逻辑需手动实现 ****:每个业务需独立编写 Try、Confirm、Cancel 接口,代码量增加 2–3 倍(如订单创建需分别实现库存预留、确认扣减、释放预留)
    • 与业务逻辑耦合 ****:事务代码嵌入业务层,重构或扩展时代价较高
  2. 🔄 ****补偿机制复杂度高
    • 回滚逻辑复杂 ****:Cancel 阶段需精准释放多类型资源(如同时回滚数据库更新、撤回 MQ 消息、恢复缓存),设计不当易导致数据不一致
    • 异常场景处理繁琐 ****:需额外处理以下问题:
      • 空回滚 ****:未执行 Try 却触发 Cancel(需通过事务 ID 校验跳过操作)
      • 防悬挂 ****:Cancel 先于 Try 执行时,需阻止后续 Try 操作
  3. 📉 ****运维与监控挑战
    • 事务状态跟踪难 **:需维护全局事务日志(如 Redis 或 DB 记录 XID 状态),增加系统复杂度
    • 长事务风险 ****:若 Confirm/Cancel 阶段延迟,预留资源可能长期占用(如冻结资金未及时释放)

✅ TCC 使用场景 高并发短事务,跨异构资源事务,和2cp,3cp一直都是并行调用参与者

电商下单、秒杀库存(Try 阶段快速预留,Confirm 异步提交,组合支付(银行卡+积分)、多存储系统(DB + Redis

**一、Saga

1. 事务拆分与补偿链

  • 正向事务链 :将全局事务拆分为多个连续的本地子事务(如电商下单流程:创建订单 → 扣减库存 → 支付扣款)
  • 补偿事务链 :为每个正向操作定义逆向补偿操作(如支付失败 → 退款 → 恢复库存 → 取消订单),形成反向补偿链。

2. 执行规则

  • 成功场景 :顺序执行所有子事务(T₁ → T₂ → T₃)。
  • 失败场景 :若子事务 Tᵢ 失败,则按 逆序执行补偿操作 (Cᵢ → Cᵢ₋₁ → … → C₁)

3. 最终一致性保证

  • 通过重试机制和补偿操作,确保系统最终达到一致状态,但允许中间状态短暂不一致(如订单显示“处理中”但库存未扣减)

一、Saga 事务的核心优点

将长事务拆分为多个独立子事务,各服务仅需关注本地逻辑,降低耦合度,

在 Saga 模式中, 并行子事务的回滚触发机制 需要结合事务依赖关系、补偿策略和状态管理来实现。以下是其核心流程与关键设计:

一、 并行回滚的触发条件

  1. 任一子事务失败
    • 当某个并行执行的子事务 执行失败 (如超时、网络异常、业务校验不通过),Saga 会立即触发全局回滚流程。
  2. 补偿失败超时
    • 若补偿操作(Compensating Action)执行失败且超过重试阈值,部分 Saga 实现(如 Seata)会强制标记事务为失败,需人工介入处理。

二、 回滚触发流程

1. 基于事件驱动的 Saga(无协调器)

  • 步骤
    1. 子事务 A 和 B 并行执行,均成功后发布 TransactionSucceeded事件。
    2. 若子事务 A 失败,发布 TransactionFailed事件。
    3. 其他参与者(如 B、C)监听失败事件,触发对应的补偿操作(如 CancelB、CancelC)。
  • 特点
    1. 无全局状态管理 ,依赖事件广播实现补偿链式触发。
    2. 补偿顺序不确定 ,需业务自行保证逆向依赖关系(如 B 依赖 A,则 A 失败需先补偿 B)。

2. 基于协调器的 Saga(如 Seata、dtm)

  • 步骤
    1. 协调器记录并行子事务的执行状态(如成功/失败)。
    2. 检测到任一子事务失败,协调器标记全局事务为 Abort。
    3. 反向拓扑顺序 触发补偿(如先补偿 C,再补偿 B,最后补偿 A)。
  • 特点
    1. 状态集中管理 ,确保补偿顺序正确。
    2. 支持保存点(Savepoint),允许从中间状态恢复(如重试部分操作)。
  1. ⏱️ 高性能与低资源阻塞
    • 无全局锁 :每个本地事务提交后立即释放资源(如数据库连接、行锁),避免长时间阻塞,显著提升吞吐量(如电商系统可支持万级 TPS)
    • 异步执行 :支持编排式(Orchestration)或协同式(Choreography)实现异步流程,减少同步等待时间

2 、 🌐 灵活适配复杂业务

    • 跨异构系统 :可整合数据库、消息队列、第三方 API 等异构资源(如同时操作 MySQL 和 Redis)37
    • 长流程支持 :天然适合多步骤业务流程(如电商订单:创建→扣库存→支付→发货),允许动态调整执行路径110
    • 异步解耦 将长事务拆分为多个独立子事务,各服务仅需关注本地逻辑,降低耦合度。单个子事务失败不会导致全局回滚,仅触发局部补偿

3、 ⚙️ 高容错性

    • 部分服务宕机时,可通过重试机制或人工介入恢复,避免全局阻塞

二、Saga 事务的核心缺点

  1. 🧩 补偿逻辑复杂
    • 开发成本高 :需为每个正向操作编写逆向补偿逻辑(如“支付成功但物流发货失败”需触发“退货退款”),代码量增加 30% 以上
    • 不可逆操作风险 :若补偿无法执行(如已发货的物流),需人工介入或设计替代方案(如仅退款不退货)
  2. 🔍 数据隔离性缺失
    • 脏读风险 :中间状态可能被其他服务读取(如订单显示“已创建”但库存未扣减),需业务层处理(如标记“处理中”状态)
  3. 🐞 调试与监控困难
    • 分布式追踪复杂 :需全局事务 ID 串联多服务日志,调试故障需跨系统排查(如支付超时需检查订单、支付、MQ 服务日志)
  4. 长事务资源占用
    • 业务锁竞争 :如库存预占后未及时释放(需设置 TTL 自动过期),可能导致超卖
推荐使用 Saga 的场景​
​场景类型​​案例与说明​
​跨服务长流程业务​电商订单(创建→扣库存→支付→发货),流程步骤多、耗时长
​最终一致性场景​旅行预订(机票+酒店+租车),允许分钟级延迟
​异构系统集成​第三方支付 + 自建库存系统,无法统一事务管理
​高并发写入​秒杀系统库存扣减,需快速释放资源
  • ​Saga 更适合串行场景​​:通过补偿机制处理长事务的最终一致性,适合电商、物流等业务流程。
  • ​TCC 更适合并行场景​​:通过资源预留和两阶段提交保障强一致性,适合金融、高并发交易。

三段式提交流程

事务消息通过三个阶段协调生产者与消息队列的行为:

  1. 半消息发送( Half Message
    • 生产者发送消息到MQ Broker,但该消息被标记为 “暂不可投递” ****状态(即半消息),存储在特殊Topic(如RMQ_SYS_TRANS_HALF_TOPIC)中,消费者无法感知24
    • 目的 ****:避免本地事务未完成时,下游提前消费导致数据不一致。
  2. 本地事务执行与二次确认
    • 生产者收到半消息成功响应后, 执行本地事务 ****(如订单创建、库存扣减)34
    • 根据事务结果向Broker发送指令:
      • Commit :半消息转为正式消息,投递给消费者。
      • Rollback :删除半消息,事务终止15
  3. 事务状态回查(补偿机制)
    • 若生产者未及时发送Commit/Rollback(如宕机),Broker会 主动回查 ****生产者,询问本地事务状态34
    • 生产者需实现回查接口,返回事务结果(Commit/Rollback),确保状态最终确定45
事务消息

关键技术保障

  1. 半消息隔离机制
    • 半消息存储于独立Topic,通过替换原消息的Topic/Queue属性实现消费者不可见,避免脏读5
  2. 事务状态持久化与恢复
    • Broker将半消息和OP消息(标记最终状态)持久化到磁盘事务日志,即使Broker宕机重启后也能恢复状态24
  3. 回查策略优化
    • 超时触发 ****:半消息超过transactionTimeOut(默认60秒)未确认时触发回查4
    • 指数退避重试 ****:回查失败后按指数退避策略(如2ⁿ秒间隔)重试,上限可配置(默认15次)4
  4. 消费端幂等性
    • 因网络重试可能导致消息重复投递,消费者需通过 唯一业务****ID+ 状态校验 ****实现幂等处理(如Redis记录已处理ID)
本地消息表

、核心原理

  1. 事务原子性绑定
    • 业务操作(如订单创建)与消息记录(如库存扣减通知)在 同一个本地事务 ****中完成,确保二者同时成功或失败156
    • 示例:创建订单时,同步插入一条状态为“待发送”的消息到本地消息表。
  2. 异步消息投递
    • 通过 定时任务 ****扫描消息表中“待发送”的记录,将消息投递到消息队列(如RocketMQ、Kafka)26
    • 发送成功后更新消息状态为“已发送”;失败则递增重试次数,超过阈值标记为“死亡消息”36
  3. 消费端幂等性
    • 消费者需保证多次处理同一消息的结果一致(如通过唯一业务ID去重)56
    • 示例:库存服务通过Redis缓存已处理的订单ID,避免重复扣减。