# 分布式事务剖析


<!--more-->


## 1. 分布式事务理论

CAP, BASE理论, 可参考 **[分布式架构理论]** 分类查看详情

## 2. 分布式事务模式

在 **[分布式架构理论]**,   **[分布式事务]**分类也有相关介绍

了解了分布式事务中的强一致性和最终一致性理论，下面介绍几种常见的分布式事务的解决方案。

- 2PC : 两阶段提交, 强一致性

  > 阶段1：准备阶段
  >
  > 协调者向所有参与者发送事务内容，询问是否可以提交事务，并等待所有参与者答复。各参与者执行事务操作，但不提交事务，将undo和redo信息记入事务日志中。如参与者执行成功，给协调者反馈yes；如执行失败，给协调者反馈no。
  >
  > 阶段2：提交阶段
  >
  > 如果协调者收到了参与者的失败消息或者超时，直接给每个参与者发送回滚(rollback)消息；否则，发送提交(commit)消息。

  > 2PC方案实现起来简单，实际项目中使用比较少，主要因为以下问题：
  >
  > - 性能问题：所有参与者在事务提交阶段处于同步阻塞状态，占用系统资源，容易导致性能瓶颈。
  > - 可靠性问题：如果协调者存在单点故障问题，如果协调者出现故障，参与者将一直处于锁定状态。
  > - 数据一致性问题：在阶段2中，如果发生局部网络问题，一部分事务参与者收到了提交消息，另一部分事务参与者没收到提交消息，那么就导致了节点之间数据的不一致。

- 3PC : 三阶段提交, 强一致性

  3PC三阶段提交，是两阶段提交的改进版本，与两阶段提交不同的是，引入超时机制。同时在协调者和参与者中都引入超时机制。三阶段提交将两阶段的准备阶段拆分为2个阶段，插入了一个**preCommit**阶段，**解决了原先在两阶段提交中，参与者在准备之后，由于协调者或参与者发生崩溃或错误，而导致参与者无法知晓处于长时间等待的问题**。如果在指定的时间内协调者没有收到参与者的消息则默认失败。

  > 阶段1：canCommit 
  >
  > 协调者向参与者发送commit请求，参与者如果可以提交就返回yes响应，否则返回no响应。
  >
  > 阶段2：preCommit 
  >
  > 协调者根据阶段1 canCommit参与者的反应情况执行预提交事务或中断事务操作。
  >
  > - 参与者均反馈yes：协调者向所有参与者发出preCommit请求，参与者收到
  >   preCommit请求后，执行事务操作，但不提交；将undo和redo信息记入事务日志中；各参与者向协调者反馈ack响应或no响应，并等待最终指令。
  > - 任何一个参与者反馈no或等待超时：协调者向所有参与者发出abort请求，无论收到协调者发出的abort请求，或者在等待协调者请求过程中出现超时，参与者均会中断事务。
  >
  > 阶段3：doCommit 
  >
  > 该阶段进行真正的事务提交，根据阶段2 preCommit反馈的结果完成事务提交或中断操作。

  相比2PC模式，3PC模式降低了阻塞范围，在等待超时后协调者或参与者会中断事务。避免了协调者单点问题，阶段3中协调者出现问题时（比如网络中断等），参与者会继续提交事务。

- XA（强一致性）

  XA是由X/Open组织提出的分布式事务的规范，是基于两阶段提交协议。XA规范主要定义了全局事务管理器（TM）和局部资源管理器（RM）之间的接口。目前主流的关系型数据库产品都是实现了XA接口。

  ![img_81](https://img.zhaojq.top/20260727091909944.png "img_81")

  > XA之所以需要引入事务管理器，是因为在分布式系统中，从理论上讲两台机器理论上无法达到一致的状态，需要引入一个单点进行协调。由全局事务管理器管理和协调的事务，可以跨越多个资源（数据库）和进程。
  >
  >  
  >
  > 事务管理器用来保证所有的事务参与者都完成了准备工作(第一阶段)。如果事务管理器收到所有参与者都准备好的消息，就会通知所有的事务都可以提交了（第二阶段）。MySQL在这个XA事务中扮演的是参与者的角色，而不是事务管理器。

- TCC模式（最终一致性）

  TCC（Try-Conﬁrm-Cancel）的概念，最早是由PatHelland于2007年发表的一篇名为《Life beyond Distributed Transactions:anApostate’sOpinion》的论文提出。TCC是服务化的两阶段编程模型，其Try、Conﬁrm、Cancel3个方法均由业务编码实现

  > TCC模式相比于XA，解决了如下几个缺点：
  >
  > - 解决了协调者单点，由主业务方发起并完成这个业务活动。业务活动管理器可以变成多点，引入集群。
  > - 同步阻塞：引入超时机制，超时后进行补偿，并且不会锁定整个资源，将资源转换为业务逻辑形式，粒度变小。
  > - 数据一致性，有了补偿机制之后，由业务活动管理器控制一致性。

- 消息队列模式（最终一致性）

  消息队列的方案最初是由eBay提出，基于**TCC**模式，消息中间件可以基于Kafka、RocketMQ等消息队列。此方案的核心是将分布式事务拆分成本地事务进行处理，将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或MQ中间件，再通过业务规则人工发起重试。

  ![img_82](https://img.zhaojq.top/20260727091910138.png "img_82")

  > - 步骤1：事务主动方处理本地事务。
  >
  >   事务主动方在本地事务中处理业务更新操作和MQ写消息操作。
  >
  > - 步骤2：事务主动方通过消息中间件，通知事务被动方处理事务通知事务待消息。
  >
  >   事务主动方主动写消息到MQ，事务消费方接收并处理MQ中的消息。
  >
  > - 步骤3：事务被动方通过MQ中间件，通知事务主动方事务已处理的消息，事务主动方根据反馈结果提交或回滚事务。

  > 为了数据的一致性，当流程中遇到错误需要重试，容错处理规则如下：
  >
  > - 当步骤1处理出错，事务回滚，相当于什么都没发生。
  > - 当步骤2处理出错，由于未处理的事务消息还是保存在事务发送方，可以重试或撤销本地业务操作。
  > - 如果事务被动方消费消息异常，需要不断重试，业务处理逻辑需要保证幂等。
  > - 如果是事务被动方业务上的处理失败，可以通过MQ通知事务主动方进行补偿或者事务回滚。
  > - 如果多个事务被动方已经消费消息，事务主动方需要回滚事务时需要通知事务被动方回滚。

- Saga模式（最终一致性）

  Saga这个概念源于1987年普林斯顿大学的Hecto和Kenneth发表的一篇数据库论文Sagas，一个Saga事务是一个有多个短时事务组成的长时的事务。在分布式事务场景下，我们把一个Saga分布式事务看做是一个由多个本地事务组成的事务，每个本地事务都有一个与之对应的补偿事务。在Saga事务的执行过程中，如果某一步执行出现异常，Saga事务会被终止，同时会调用对应的补偿事务完成相关的恢复操作，这样保证Saga相关的本地事务要么都是执行成功，要么通过补偿恢复成为事务执行之前的状态。（自动反向补偿机制）。

  > Saga事务基本协议如下：
  >
  > - 每个Saga事务由一系列幂等的有序子事务(sub-transaction)Ti组成。
  > - 每个Ti都有对应的幂等补偿动作Ci，补偿动作用于撤销Ti造成的结果。
  >
  >  
  >
  > Saga是一种补偿模式，它定义了两种补偿策略：
  >
  > - 向前恢复（forward recovery）：对应于上面第一种执行顺序，发生失败进行重试，适用于必须要成功的场景。
  > - 向后恢复（backward recovery）：对应于上面提到的第二种执行顺序，发生错误后撤销掉之前所有成功的子事务，使得整个Saga的执行结果撤销。
  >
  > ![img_83](https://img.zhaojq.top/20260727091910330.png "img_83")

- Seata 可参考seata模块教程

## 3. Sharding-JDBC整合XA原理

Java通过定义JTA接口实现了XA的模型，JTA接口里的**ResourceManager**需要**数据库厂商提供XA的驱动**实现，而**TransactionManager**则需要**事务管理器的厂商**实现，传统的事务管理器需要同应用服务器绑定，因此使用的成本很高。而嵌入式的事务管器可以以jar包的形式提供服务，同ShardingSphere集成后，可保证分片后跨库事务强一致性。

ShardingSphere支持以下功能：

- 支持数据分片后的跨库XA事务
- 两阶段提交保证操作的原子性和数据的强一致性服务宕机重启后，提交/回滚中的事务可自动恢复
- SPI机制整合主流的XA事务管理器，默认Atomikos 同时支持XA和非XA的连接池
- 提供spring-boot和namespace的接入端

ShardingSphere整合XA事务时，分离了XA事务管理和连接池管理，这样接入XA时，可以做到对业务的零侵入。

![img_84](https://img.zhaojq.top/20260727091910506.png "img_84")

- Begin（开启XA全局事务）

  XAShardingTransactionManager会调用具体的XA事务管理器开启XA的全局事务。

- 执行物理SQL 

  ShardingSphere进行解析/优化/路由后会生成SQL操作，执行引擎为每个物理SQL创建连接的同时，物理连接所对应的XAResource也会被注册到当前XA事务中。事务管理器会在此阶段发送XAResource.start命令给数据库，数据库在收到XAResource.end命令之前的所有SQL操作，会被标记为XA事务。

  例如:

  ```
  XAResource1.start ## Enlist阶段执行
  statement.execute("sql1"); ## 模拟执行一个分片SQL1
  statement.execute("sql2"); ## 模拟执行一个分片SQL2
  XAResource1.end ## 提交阶段执行
  ```

- Commit/rollback（提交XA事务）

  XAShardingTransactionManager收到接入端的提交命令后，会委托实际的XA事务管理进行提交动作，这时事务管理器会收集当前线程里所有注册的XAResource，首先发送XAResource.end指令，用以标记此XA事务的边界。接着会依次发送prepare指令，收集所有参与XAResource投票，如果所有XAResource的反馈结果都是OK，则会再次调用commit指令进行最终提交，如果有一个XAResource的反馈结果为No，则会调用rollback指令进行回滚。在事务管理器发出提交指令后，任何XAResource产生的异常都会通过recovery日志进行重试，来保证提交阶段的操作原子性，和数据强一致性。
  例如:

  ```
  XAResource1.prepare     ##ack:yes 
  XAResource2.prepare     ##ack:yes 
  XAResource1.commit 
  XAResource2.commit
  -----------
  XAResource1.prepare     ##ack:yes 
  XAResource2.prepare     ##ack:no 
  XAResource1.rollback 
  XAResource2.rollback
  ```

## 4. Sharding-JDBC整合Saga原理

ShardingSphere的柔性事务已通过第三方servicecomb-saga组件实现的，通过SPI机制注入使用。

ShardingSphere是基于反向SQL技术实现的反向补偿操作，它将对数据库进行更新操作的SQL自动生成反向SQL，并交由Saga-actuator引擎执行。使用方则无需再关注如何实现补偿方法，将柔性事务管理器的应用范畴成功的定位回了事务的本源——数据库层面。

ShardingSphere支持以下功能：

- 完全支持跨库事务
- 支持失败SQL重试及最大努力送达
- 支持反向SQL、自动生成更新快照以及自动补偿
- 默认使用关系型数据库进行快照及事务日志的持久化，支持使用SPI的方式加载其他类型的持久化

Saga柔性事务的实现类为SagaShardingTransactionMananger,ShardingSphere通过Hook的方式拦截逻辑SQL的解析和路由结果，这样，在分片物理SQL执行前，可以生成逆向SQL，在事务提交阶段再把SQL调用链交给Saga引擎处理。

![img_85](https://img.zhaojq.top/20260727091910747.png "img_85")

- Init（Saga引擎初始化）

  包含Saga柔性事务的应用启动时，saga-actuator引擎会根据saga.properties的配置进行初始化的流程。

- Begin（开启Saga全局事务）

  每次开启Saga全局事务时，将会生成本次全局事务的上下文（SagaTransactionContext），事务上下文记录了所有子事务的正向SQL和逆向SQL，作为生成事务调用链的元数据使用。

- 执行物理SQL

  在物理SQL执行前，ShardingSphere根据SQL的类型生成逆向SQL，这里是通过Hook的方式拦截Parser的解析结果进行实现。

- Commit/rollback（提交Saga事务）

  提交阶段会生成Saga执行引擎所需的调用链路图，commit操作产生ForwardRecovery（正向SQL 补偿）任务，rollback操作产生BackwardRecovery任务（逆向SQL补偿）。

## 5. Sharding-JDBC整合Seata原理

分布式事务的实现目前主要分为两阶段的XA强事务和BASE柔性事务。

![img_86](https://img.zhaojq.top/20260727091910997.png "img_86")

**Seata AT**事务作为**BASE**柔性事务的一种实现，可以无缝接入到ShardingSphere生态中。在整合Seata  AT事务时，需要把TM，RM，TC的模型融入到ShardingSphere分布式事务的SPI的生态中。在数据库资源上，Seata通过对接DataSource接口，让JDBC操作可以同TC进行RPC通信。同样，ShardingSphere也是面向DataSource接口对用户配置的物理DataSource进行了聚合，因此把物理DataSource二次包装为Seata的DataSource后，就可以把Seata AT事务融入到ShardingSphere的分片中。

![img_87](https://img.zhaojq.top/20260727091911327.png "img_87")

- Init（Seata引擎初始化）

  包含Seata柔性事务的应用启动时，用户配置的数据源会按seata.conf的配置，适配成Seata事务所需的DataSourceProxy，并且注册到RM中。

- Begin（开启Seata全局事务）

  TM控制全局事务的边界，TM通过向TC发送Begin指令，获取全局事务ID，所有分支事务通过此全局事务ID，参与到全局事务中；全局事务ID的上下文存放在当前线程变量中。

- 执行分片物理SQL 

  处于Seata全局事务中的分片SQL通过RM生成undo快照，并且发送participate指令到TC，加入到全局事务中。ShardingSphere的分片物理SQL是按多线程方式执行，因此整合SeataAT事务时，需要在主线程和子线程间进行全局事务ID的上下文传递，这同服务间的上下文传递思路完全相
  同。

- Commit/rollback（提交Seata事务）

  提交Seata事务时，TM会向TC发送全局事务的commit和rollback指令，TC根据全局事务ID协调所有分支事务进行commit和rollback。












