# 补偿事务（TCC）

**TCC（Try-Confirm-Cancel）**&#x53C8;被称补偿事务，是一种应用层面侵入业务的两阶段提交，是一种**柔性事务方案**，其核心思想是：**针对每个操作，都要注册一个与其对应的确认（Try）和补偿（Cancel）操作。**

1. **第一阶段**\
   Try（尝试）：主要是对业务系统做检测及资源预&#x7559;**（加锁，锁住资源）**
2. **第二阶段**\
   本阶段根据第一阶段的结果，决定是执行 Confirm 还是 Cancel
   * [x] **Confirm（确认）：**&#x6267;行真正的业务（执行业务，释放锁）
   * [x] **Cancel（取消）**：是预留资源的取消（出问题，释放锁）

<figure><img src="https://1859307470-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FbAEe9BsXOIGgUppwedsA%2Fuploads%2FmWf3NxmYG7TMSKCNGVi8%2Ftcc.png?alt=media&#x26;token=f5b7d022-f9e8-4789-85b4-414278566a3f" alt=""><figcaption></figcaption></figure>

## **案例**

以电商下单为例，这里把整个过程简单分为扣减库存，订单创建 2 个步骤，库存服务和订单服务分别在不同的服务器节点上。

假设商品库存为 100，购买数量为 2，这里检查和更新库存的同时，冻结用户购买数量的库存，同时创建订单，订单状态为待确认。

### **Try 阶段**

**TCC 机制中的 Try 仅是一个初步操作，它和后续的确认一起才能真正构成一个完整的业务逻辑**，这个阶段主要完成：

* 完成所有业务检查（一致性）。
* 预留必须业务资源（准隔离性）。
* Try 尝试执行业务。

<div align="left"><figure><img src="https://1859307470-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FbAEe9BsXOIGgUppwedsA%2Fuploads%2F6PQY3Y6yrwGFJAsErYJv%2Ftry.png?alt=media&#x26;token=bc3558d0-8586-4d64-aa90-5d514e9a3632" alt="" width="432"><figcaption></figcaption></figure></div>

### **Confirm / Cancel 阶段**

根据 Try 阶段服务是否全部正常执行，继续执行确认操作（Confirm）或取消操作（Cancel）。\
Confirm 和 Cancel 操作满足幂等性，如果 Confirm 或 Cancel 操作执行失败，将会不断重试直到执行完成。

**Confirm：当 Try 阶段服务全部正常执行， 执行确认业务逻辑操作。**

<figure><img src="https://1859307470-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FbAEe9BsXOIGgUppwedsA%2Fuploads%2FiYricjLnYVTtaMbExFqp%2Fconfirm.png?alt=media&#x26;token=f2893c25-6b15-4b0b-84ac-a5267d798d8c" alt=""><figcaption></figcaption></figure>

这里使用的资源一定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为，如果在 Try 阶段能正常的预留资源，那 Confirm 一定能完整正确的提交。

Confirm 阶段也可以看成是对 Try 阶段的一个补充，Try+Confirm 一起组成了一个完整的业务逻辑。

**Cancel：当 Try 阶段存在服务执行失败， 进入 Cancel 阶段。**

<figure><img src="https://1859307470-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FbAEe9BsXOIGgUppwedsA%2Fuploads%2FAVsQVSkNpWENGf9RCdGp%2Fconcel.png?alt=media&#x26;token=d8b03734-92be-4f70-9128-a3f4a7b5138f" alt=""><figcaption></figcaption></figure>

Cancel 取消执行，释放 Try 阶段预留的业务资源，上面的例子中，Cancel 操作会把冻结的库存释放，并更新订单状态为取消。

## **最终一致性保证**

* TCC 事务机制以初步操作（Try）为中心的，确认操作（Confirm）和取消操作（Cancel）都是围绕初步操作（Try）而展开。因此，Try 阶段中的操作，其保障性是最好的，即使失败，仍然有取消操作（Cancel）可以将其执行结果撤销。
* Try 阶段执行成功并开始执行 Confirm 阶段时，默认 Confirm 阶段是不会出错的。也就是说只要 Try 成功，Confirm 一定成功 。
* <mark style="color:blue;">**Confirm 与 Cancel 如果失败，由 TCC 框架进行重试。**</mark>
* <mark style="color:blue;">**存在极低概率在 Confirm 与 Cancel 环节彻底失败，则需要定时任务或人工介入。**</mark>

## **TCC中的特别处理**

### 空回滚

**出现原因：**&#x5206;支事务所在系统服务宕机或者网络波动，分支事务的调用是失败的，该情况下其实分支事务没有进行 Try 操作，当故障恢复后，分布式事务进行了回滚则会调用二阶段的 Cancel 方法，既而形成了空回滚。

**解决方法：**&#x5DF2;知全局事务 id 会贯穿整个全局分布式事务的调用链，额外增加一张分支事务记录表，其中有全局事务 id 和分支事务 id，每一次成功的 Try 执行后插入一条分支事务执行记录，第二阶段 Cancel 执行时读取该表记录，如果该分支事务对应的执行记录存在，就回滚，如果不存在就认为是空回滚，直接返回成功。

### 幂等问题

**出现原因：**&#x7531;于服务宕机或者网络问题，方法的调用可能出现超时，为了保证事务正常执行往往会加入重试的机制，因此**必须保证 Confirm 和 Cancel 阶段操作的幂等性**。

**解决方法：可以在分支事务记录表中增加事务执行状态，每次执行 Confirm 和 Cancel 方法时都查询该事务的执行状态，以此判断事务的幂等性。**

### 悬挂问题

**出现原因：**&#x54;CC 中，在调用 Try 之前会先注册分支事务，注册分支事务之后，调用出现超时，此时 Try 请求还未到达对应的服务，因为调用超时了，所以会执行 Cancel 调用。当 Cancel 执行完后，之前发送的 Try 请求却到达了，而这个迟到的 Try 操作不会有后续的 Confirm 或 Cancel，这样会导致资源挂起，无法释放。

**解决方法：**&#x6267;行 Try 方法时借助分支事务表中事务的执行状态，可以判断 Confirm 或者 Cancel 方法是否已经执行，如果执行了那么就不执行 Try 阶段。

### **总结**

* 在 TCC 模式下，所有操作要保证幂等性
* 需要有事务的执行记录
* 执行 Try 和 Cancel 时候都需要进行操作记录判断

## 优缺点

**优势**：TCC 执行的每一阶段都会提交本地事务并释放锁，并不需要等待其他事务的执行结果。而如果其他事务执行失败，最后不是回滚，而是执行补偿操作。这样就避免了资源的长期锁定和阻塞等待，**执行效率比较高**，属于性能比较好的分布式事务方式。

**缺点**：

* 代码侵入：需要编写代码实现 Try 、Confirm、 Cancel
* 开发成本高：一个业务需要拆分成 3 个步骤，分别编写业务实现，业务编写比较复杂
* 安全性考虑：Cancel 动作如果执行失败，资源就无法释放，需要引入重试机制，而重试导致重复执行，需要考虑重试的幂等性问题
