# 【NO.145】分布式事务解决方案

## **1.简述**

分布式事务是指事务的操作位于不同的节点上，需要保证事务的ACID特性。在分布式架构下，每个节点只知晓自身操作的成功与失败，无法知悉其他节点的操作状态。当一个事务跨多个节点时，为了保持事务的原子性与一致性，从而引入一个协调者来统一管控所有参与者的操作结果，并指引它们最终是否把操作结果进行诊治的提交(commit)和回滚(rollback)。例如在购物下单场景中，库存和订单如果不在同一个节点上，就涉及分布式事务。

## **2.解决方案**

在分布式系统中要实现分布式事务，常见的解决方案有两段提交(2PC)、三段提交(3PC)、事务补偿(TCC)、本地消息表(异步确保)、MQ事务方案(可靠消息事务)、最大努力通知和Saga事务。

### **2.1 两阶段提交（2PC）**

![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213214017264-1669488684.png)

二阶段提交协议（Two-phase Commit，即 2PC）是常用的分布式事务解决方案，即将事务的提交过程分为准备阶段和提交阶段两个阶段来进行处理，通过引入协调者（Coordinator）来协调参与者的行为，并最终决定这些参与者是否要真正执行事务。

- 事务协调者（事务管理器）：事务的发起者
- 事务参与者（资源管理器）：事务的执行者

**准备阶段（投票阶段）**

协调者询问参与者事务是否执行成功，参与者发回事务执行结果，但该阶段并未提交事务。

1. 协调者向所有参与者发送事务内容，询问是否可以提交事务，并等待答复；
2. 各参与者执行事务操作，将 undo 和 redo 信息记入事务日志中（但不提交事务）；
3. 如参与者执行成功，给协调者反馈同意，否则反馈终止。

**提交阶段（执行阶段）**

如果事务在每个参与者上都执行成功，事务协调者发送通知让参与者提交事务；否则，协调者发送通知让参与者回滚事务。

在准备阶段，参与者执行了事务，但是还未提交。只有在提交阶段接收到协调者发来的通知后，才进行提交或者回滚。

1. 事务协调者节点向所有参与者节点发出正式提交(`commit`)的请求；
2. 参与者节点正式完成操作，并释放在整个事务期间内占用的资源；
3. 参与者节点向协调者节点发送ACK完成消息；
4. 事务协调者节点收到所有参与者节点反馈的ACK完成消息后，完成事务。

**2PC优缺**

**优点**

- 尽量保证了数据的强一致，适合对数据强一致要求很高的关键领域。（其实也不能100%保证强一致，如宕机）

**缺点**

- **性能问题**：执行过程中，所有参与节点都是事务阻塞型的。当参与者占有公共资源时，其他第三方节点访问公共资源不得不处于阻塞状态。
- **可靠性问题**：参与者发生故障。协调者需要给每个参与者额外指定超时机制，超时后整个事务失败。协调者发生故障。参与者会一直阻塞下去。需要额外的备机进行容错。
- **数据一致性问题**：二阶段无法解决的问题如协调者在发出`commit`消息之后宕机，而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者，这条事务的状态也是不确定的，没人知道事务是否被已经提交。
- **实现复杂**：牺牲了可用性，对性能影响较大，不适合高并发高性能场景。

### **2.2 三阶段提交（3PC）**

![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213214104494-1390181182.png)

三阶段提交协议是二阶段提交协议的改进版本，其有两个改动点。

1. 在协调者和参与者中都引入超时机制；
2. 在第一阶段和第二阶段中插入一个准备阶段，保证了在最后提交阶段之前各参与节点的状态是一致的。

即除了引入超时机制之外，3PC把2PC的准备阶段再次一分为二，这样三阶段提交就有`CanCommit`、`PreCommit和``DoCommit`三个阶段。

**3PC优缺点**

**优点**

相比二阶段提交，三阶段提交降低了阻塞范围，在等待超时后协调者或参与者会中断事务。避免了协调者单点问题，阶段 3 中协调者出现问题时，参与者会继续提交事务。

**缺点**

数据不一致问题依然存在，当在参与者收到 `preCommit` 请求后等待 `doCommit` 指令时，此时如果协调者请求中断事务，而协调者无法与参与者正常通信，会导致参与者继续提交事务，造成数据不一致。

### **2.3 事务补偿（TCC）**

![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213193657939-2019193487.png)

TCC（Try Confirm Cancel）方案是一种应用层面侵入业务的两阶段提交，是目前最火的一种柔性事务方案。TCC采用了补偿机制，其核心思想就是针对每个操作，都要注册一个与其对应的确认和补偿（撤销）操作。它分为Try、Confirm和Cancel三个阶段。

1. Try 阶段主要是对业务系统做检测及资源预留；
2. Confirm 阶段主要是对业务系统做确认提交，Try阶段执行成功并开始执行 Confirm阶段时，默认 Confirm阶段是不会出错的，即：只要Try成功，Confirm一定成功；
3. Cancel 阶段主要是在业务执行错误，需要回滚的状态下执行的业务取消，预留资源释放。

转账例子：假入 Bob 要向 Smith 转账，思路大概是： 我们有一个本地方法，里面依次调用。

1. 首先在 Try 阶段，要先调用远程接口把 Smith 和 Bob 的钱给冻结起来；
2. 在 Confirm 阶段，执行远程调用的转账的操作，转账成功进行解冻。
3. 如果第2步执行成功，那么转账成功，如果第二步执行失败，则调用远程冻结接口对应的解冻方法 (Cancel)。

**TCC优缺点**

**优点**

跟2PC比起来，实现以及流程相对简单了一些，但数据的一致性比2PC差。

**缺点**

在2和3步中都有可能会失败。TCC属于应用层的一种补偿方式，所以需要程序员在实现的时候多写很多补偿的代码，在一些场景中，一些业务流程可能用TCC不太好定义及处理。

### **2.4 本地消息表（异步确保**）

本地消息表方案的核心思路是将分布式事务拆分成本地事务进行处理。通过在事务主动发起方额外新建事务消息表，事务发起方处理业务和记录事务消息在本地事务中完成，轮询事务消息表的数据发送事务消息，事务被动方基于消息中间件消费事务消息表中的事务。 

*![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213194544060-1699106774.png)*

上图中整体的处理步骤如下：

1. 事务主动方在同一个本地事务中处理业务和写消息表操作；
2. 事务主动方通过消息中间件，通知事务被动方处理事务通知事务待消息。消息中间件可以基于 Kafka、RocketMQ 消息队列，事务主动方主动写消息到消息队列，事务消费方消费并处理消息队列中的消息；
3. 事务被动方通过消息中间件，通知事务主动方事务已处理的消息；
4. 事务主动方接收中间件的消息，更新消息表的状态为已处理。

本地消息表优缺点

**优点**

避免了分布式事务，实现了最终一致性。

**缺点**

消息表会耦合到业务系统中，如果没有封装好的解决方案，会有很多杂活需要处理。

### **2.5 MQ事务方案(可靠消息事务)** 

MQ事务方案是对本地消息表的封装，将本地消息表基于 MQ 内部，其他方面的协议基本与本地消息表一致。

![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213195114938-2056723236.png)

### **2.6 最大努力通知**

最大努力通知方案是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口，如果事务被动方没有接收到消息，此时可以调用事务主动方提供的消息校对的接口主动获取。

其适用于业务通知类型，例如微信交易的结果，就是通过最大努力通知方式通知各个商户，既有回调通知，也有交易查询接口。

![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213201201220-874864581.png)

### **2.7 Saga事务**

Saga 事务核心思想是将长事务拆分为多个本地短事务，由 Saga 事务协调器协调，如果正常结束那就正常完成，如果某个步骤失败，则根据相反顺序一次调用补偿操作。

Saga 事务基本协议如下：

- 每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) **Ti** 组成。
- 每个 **Ti** 都有对应的幂等补偿动作 **Ci**，补偿动作用于撤销 **Ti** 造成的结果。

Saga 的执行顺序有两种：

- T1, T2, T3, ..., Tn
- T1, T2, ..., Tj, Cj,..., C2, C1，其中0 < j < n

TCC事务补偿机制有一个预留(Try)动作，相当于先报存一个草稿，然后才提交；而Saga事务没有预留动作，直接提交。对于事务异常，Saga提供了向后恢复和向前恢复两种恢复策略。

**向后恢复（backward recovery）**

backward recovery，即上面提到的第二种执行顺序，其中j是发生错误的sub-transaction，这种做法的效果是撤销掉之前所有成功的sub-transation，使得整个Saga的执行结果撤销。

![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213202619446-202874736.png)

**向前恢复（forward recovery）**

forward recovery，即适用于必须要成功的场景，执行顺序是类似于这样的：T1, T2, ..., Tj(失败), Tj(重试),..., Tn，其中j是发生错误的sub-transaction。该情况下不需要Ci。
 ![img](https://img2022.cnblogs.com/blog/1580332/202202/1580332-20220213202633035-1675437339.png)

## **3.总结**

各分布式事务方案的常见使用场景：

- **2PC/3PC**：依赖于数据库，能够很好的提供强一致性和强事务性，但相对来说延迟比较高，比较适合传统的单体应用，在同一个方法中存在跨库操作的情况，不适合高并发和高性能要求的场景。
- **TCC**：适用于执行时间确定且较短，实时性要求高，对数据一致性要求高，比如互联网金融企业最核心的三个服务：交易、支付、账务。
- **本地消息表/MQ 事务**：都适用于事务中参与方支持操作幂等，对一致性要求不高，业务上能容忍数据不一致到一个人工检查周期，事务涉及的参与方、参与环节较少，业务上有对账/校验系统兜底。
- **Saga 事务**：由于 Saga 事务不能保证隔离性，需要在业务层控制并发，适合于业务场景事务并发操作同一资源较少的情况。Saga 相比缺少预提交动作，导致补偿动作的实现比较麻烦，例如业务是发送短信，补偿动作则得再发送一次短信说明撤销，用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。

## **4.Seta**

上述几种方案都是分布式事务的理论知识，Seta是分布式解放方案的一个落地实现。

Seata 是一款开源的分布式事务解决方案，致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 **AT**、**TCC**、**SAGA** 和 **XA** 事务模式，为用户打造一站式的分布式解决方案。

- 对业务无侵入：即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入；
- 高性能：减少分布式事务解决方案所带来的性能消耗。
- Seta官方文档：https://seata.io/zh-cn/index.html 

原文作者：[涛姐涛哥](https://home.cnblogs.com/u/taojietaoge/)

原文链接：https://www.cnblogs.com/taojietaoge/p/15890289.html