04-分布式事务

分布式事务

  • 刚性事务
    • 严格遵循 ACID 原则(原子性、一致性、隔离性、持久性),要求事务在执行过程中必须满足强一致性。典型的实现是 两阶段提交(2PC) 和 三阶段提交(3PC)。
  • 柔性事务
    • 以 BASE 理论(基本可用、柔性状态、最终一致)为设计原则,允许事务在执行过程中存在短暂的不一致,但最终会通过补偿机制达成一致。典型实现包括 TCC(Try-Confirm-Cancel)、Saga 模式、本地消息表 等。

XA

  • 是一种用于分布式事务处理的规范,其核心目标是确保跨多个资源(如数据库、消息队列等)的事务操作具备 ACID 特性(原子性、一致性、隔离性、持久性)
  • XA 规范
    • X/Open 组织 提出的标准,定义了分布式事务的协调机制。
    • 基于 两阶段提交(2PC, Two-Phase Commit) 协议实现。
  • 角色定义
    • 事务管理器(TM, Transaction Manager):协调全局事务,决定提交或回滚。
    • 资源管理器(RM, Resource Manager):管理具体资源(如数据库、消息队列),执行事务操作。

2PC

事务流程

  • 协调者会给各参与者发送准备命令,你可以把准备命令理解成除了提交事务之外啥事都做完了

  • 同步等待所有资源的响应之后就进入第二阶段即提交阶段(注意提交阶段不一定是提交事务,也可能是回滚事务)。

  • 假如在第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功。

  • image-20210315115401682

  • 假如在第一阶段有一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败。

  • 二阶段提交失败

    • 第二阶段执行的是回滚事务操作,那么答案是不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直阻塞着。
    • 第二阶段执行的是提交事务操作,那么答案也是不断重试,因为有可能一些参与者的事务已经提交成功了,这个时候只有一条路,就是头铁往前冲,不断的重试,直到提交成功,到最后真的不行只能人工介入处理。
  • 2PC 是一个同步阻塞协议,像第一阶段协调者会等待所有参与者响应才会进行下一步操作,当然第一阶段的协调者有超时机制,假设因为网络原因没有收到某参与者的响应或某参与者挂了,那么超时后就会判断事务失败,向所有参与者发送回滚命令。

协调者故障

  • 协调者是一个单点,存在单点故障问题
    • 协调者在发送准备命令之前挂了,等于事务还没开始。
    • 协调者在发送准备命令之后挂了,有些参与者等于都执行了处于事务资源锁定的状态。不仅事务执行不下去,还会因为锁定了一些公共资源而阻塞系统其它操作。
    • 协调者在发送回滚事务命令之前挂了,那么事务也是执行不下去,且在第一阶段那些准备成功参与者都阻塞着。
    • 协调者在发送回滚事务命令之后挂了,很大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞着。
    • 协调者在发送提交事务命令之前挂了,所有资源都阻塞着。
    • 协调者在发送提交事务命令之后挂了,很大概率都会提交成功,然后释放资源,但是如果出现网络分区问题某些参与者将因为收不到命令而阻塞着。

2PC总结

  • 2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。
  • 2PC 适用于数据库层面的分布式事务场景
  • 缺点
    • 性能差,在准备阶段,同步阻塞,要等待所有的参与者返回,才能进入阶段二,在这期间,各个参与者上面的相关资源被排他地锁住,参与者上面意图使用这些资源的本地事务只能等待。因为存在这种同步阻塞问题,所以影响了各个参与者的本地事务并发度;
    • 准备阶段完成后,如果协调者宕机,所有的参与者都收不到提交或回滚指令,导致所有参与者“不知所措”;
    • 在提交阶段,协调者向所有的参与者发送了提交指令,如果一个参与者未返回 ACK,那么协调者不知道这个参与者内部发生了什么(由于网络二将军问题的存在,这个参与者可能根本没收到提交指令,一直处于等待接收提交指令的状态;也可能收到了,并成功执行了本地提交,但返回的 ACK 由于网络故障未送到协调者上),也就无法决定下一步是否进行全体参与者的回滚。

3PC

  • 分为三个阶段:准备阶段,预提交阶段,提交阶段 CanCommit、PreCommit 和 DoCommit

    • CanCommit阶段:协调者向所有参与者发送事务内容,询问是否可执行事务。参与者反馈Yes(可执行)或No(不可执行) 。
    • PreCommit阶段:若所有参与者均返回Yes,协调者发送预提交请求,参与者执行事务操作并记录Undo/Redo日志,但暂不提交;若任一参与者返回No或超时未响应,协调者中断事务 。
    • DoCommit阶段:协调者根据PreCommit结果发送提交(Commit)或回滚(Abort)指令。参与者执行最终提交或回滚,并释放资源 。

改进点

  • 引入超时机制:协调者和参与者均设置超时时间。若参与者未收到协调者的最终指令,可在超时后自动提交事务,避免长期阻塞
  • 新增缓冲阶段:通过CanCommit和PreCommit的分离,确保在提交前所有参与者状态一致,降低数据不一致风险

TCC

  • 2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务
  • TCC 是一种服务层面上的 2PC,它是如何解决 2PC 无法应对宕机问题的缺陷的呢?答案是不断重试。由于 try 操作锁住了全局事务涉及的所有资源,保证了业务操作的所有前置条件得到满足,因此无论是 confirm 阶段失败还是 cancel 阶段失败都能通过不断重试直至 confirm 或 cancel 成功(所谓成功就是所有的服务都对 confirm 或者 cancel 返回了 ACK)。

Try - Confirm - Cancel

  • try: 指的是预留,即资源的预留和锁定,注意是预留(锁资源)

  • Confirm 指的是确认操作,这一步其实就是真正的执行了。

  • Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。

  • TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。

  • 撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等

  • TCC可以跨数据库、跨不同的业务系统来实现事务

SAGA

  • 属于长事务解决方案,将长事务拆分为多个本地短事务
    • 将大事务 T 拆分成若干小事务,命名为 T1,T2,Tn,每个子事务都具备原子性。如果分布式事务 T 能够正常提交,那么它对数据的影响应该与连续按顺序成功提交子事务 Ti 等价。
    • 每个短事务都有一个补偿动作 ——> C1,C2,……..Cn
    • 如果T1,T2~ Tn 这些短事务顺利完成的话,则整个事务结束,否则将采取回复模式
  • Saga 非常适合处理流程较长、且需要保证事务最终一致性的业务场景。例如,在一个旅游预订平台中,用户可能同时预订机票、酒店和租车服务,这些服务可能由不同的微服务或第三方供应商提供。在这种场景下,Saga 事务模型允许系统逐步执行每个操作,并在任一步骤失败时有序地执行补偿操作,从而确保系统的一致性并提升用户体验。
  • 与 TCC 相比,Saga 通常采用事件驱动设计,即每个服务都是异步执行的,无需设计资源的冻结状态或处理撤销冻结的操作。但缺点是不具备隔离性,多个 Saga 小事务操作同一数据源时,无法保证操作的原子性,可能出现数据被覆盖的情况。
  • 尽管补偿操作较易实现,但确保正向操作与补偿操作的严格执行仍需要大量精力。因此,Saga 事务通常不通过裸编码实现,而是在事务中间件的支持下完成。前面提到的 Seata 中间件也支持 Saga 事务模型。

基于消息中间件的最终一致性事务方案

  • 通过解耦事务参与者异步化处理消息可靠性保证来实现数据的最终一致

本地消息表

  • 适用场景:所有服务可访问同一数据库(如单体数据库拆分的微服务)。
  • 流程
    graph LR
    A[业务服务] --> B[执行业务操作]
    B --> C[写入本地事务表]
    C --> D[提交本地事务]
    D --> E[异步发送消息到MQ]
    E --> F[消息消费者处理下游事务]
    
  • 业务服务在本地事务中:
    1. 执行业务SQL(如扣减库存)。
    2. 插入一条消息到本地消息表(含业务ID、状态待发送)。
  • 后台线程轮询消息表,将待发送消息投递到MQ。
  • 下游服务消费消息并处理业务(如创建订单),完成后发送ACK。
  • 生产者收到ACK后更新本地消息状态为已发送

MQ事务消息

  • 适用场景:支持事务消息的MQ(如RocketMQ、Kafka)。
  • 流程
    graph LR
    A[生产者] --> B[发送Half Message]
    B --> C[MQ持久化消息]
    C --> D[执行本地事务]
    D --> E{事务成功?}
    E -->|是| F[提交消息]
    E -->|否| G[回滚消息]
    F --> H[消息对消费者可见]
    H --> I[消费者处理业务]
    
  • 关键步骤
    • Half Message:生产者发送半消息(对消费者不可见)。
    • 执行本地事务:生产者执行业务逻辑(如冻结库存)。
    • Commit/Rollback
      • 成功 → MQ提交消息(消费者可见)。
      • 失败 → MQ丢弃消息。
    • 事务回查(RocketMQ特有):若生产者未响应,MQ回调生产者确认事务状态。

最大努力通知

  • 适用场景:对一致性要求较低的场景(如支付结果通知)。
  • 流程
    • 生产者完成本地事务后,异步通知消费者(通过MQ/HTTP)。
    • 若消费者处理失败,生产者按策略重试通知(如间隔1s、5s、10s)。
    • 达到重试上限后,触发人工干预。

Seata

  • Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式。其中 AT 模式是 Seata 主推的事务模式,使用 AT 有一个前提,那就是微服务使用的数据库必须是支持事务的关系型数据库。**

AT、TCC、Saga、XA 模式分析

  • 分布式事务模式 介绍 技术栈
    AT 模式 无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本(sql都由框架托管统一执行,会存在脏写问题) seata、shardingsphere
    TCC 模式 高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景(第一阶段会产生行锁,事务执行太久会锁行很久 seata、service-comb
    Saga 模式 长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统(第一阶段就操作DB,会存在脏读问题) seata、shardingsphere、service-comb
    XA模式 分布式强一致性的解决方案,但性能低而使用较少。 seata、shardingsphere

Seata的工作流程

  • Seata 的 AT 模式建立在关系型数据库的本地事务特性的基础之上,通过数据源代理类拦截并解析数据库执行的 SQL,记录自定义的回滚日志,如需回滚,则重放这些自定义的回滚日志即可。AT 模式虽然是根据 XA 事务模型(2PC)演进而来的,但是 AT 打破了 XA 协议的阻塞性制约,在一致性和性能上取得了平衡。
  • AT 模式是基于 XA 事务模型演进而来的,它的整体机制也是一个改进版本的两阶段提交协议。AT 模式的两个基本阶段是:
    • 第一阶段:首先获取本地锁,执行本地事务,业务数据操作和记录回滚日志在同一个本地事务中提交,最后释放本地锁;
    • 第二阶段:如需全局提交,异步删除回滚日志即可,这个过程很快就能完成。如需要回滚,则通过第一阶段的回滚日志进行反向补偿。

实例

  • 协调者 shopping-service 先调用参与者 repo-service 扣减库存,后调用参与者 order-service 生成订单。这个业务流使用 Seata in XA mode 后的全局事务流程如下图所示:

    1. shopping-service 向 Seata 注册全局事务,并产生一个全局事务标识 XID
    2. 将 repo-service.repo_db、order-service.order_db 的本地事务执行到待提交阶段,事务内容包含对 repo-service.repo_db、order-service.order_db 进行的查询操作以及写每个库的 undo_log 记录
    3. repo-service.repo_db、order-service.order_db 向 Seata 注册分支事务,并将其纳入该 XID 对应的全局事务范围
    4. 提交 repo-service.repo_db、order-service.order_db 的本地事务
    5. repo-service.repo_db、order-service.order_db 向 Seata 汇报分支事务的提交状态
    6. Seata 汇总所有的 DB 的分支事务的提交状态,决定全局事务是该提交还是回滚
    7. Seata 通知 repo-service.repo_db、order-service.order_db 提交/回滚本地事务,若需要回滚,采取的是补偿式方法
    8. 1)2)3)4)5)属于第一阶段,6)7)属于第二阶段。
  • 分支的本地事务在第一阶段提交完成之后,就会释放掉本地事务锁定的本地记录。这是 AT 模式和 XA 最大的不同点


04-分布式事务
https://x-leonidas.github.io/2022/02/01/12分布式系统/04-分布式事务/
作者
听风
发布于
2022年2月1日
更新于
2025年6月5日
许可协议