1. 什么是TCC
TCC 基于分布式事务中的二阶段提交协议实现,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下:
- Try:对业务资源的检查并预留;
- Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功;
- Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放。
作者:微信小助手
发布时间:2025-01-18T23:11:42
1. 什么是TCC TCC 基于分布式事务中的二阶段提交协议实现,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下: TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。 常见开源TCC框架: 2. 以用户下单为例 try-commit try 阶段首先进行预留资源,然后在 commit 阶段扣除资源。如下图:
try-cancel try 阶段首先进行预留资源,预留资源时扣减库存失败导致全局事务回滚,在 cancel 阶段释放资源。如下图: 3. Seata TCC 模式 一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的: 在Seata中,AT模式与TCC模式事实上都是两阶段提交的具体实现,他们的区别在于: AT 模式基于 支持本地 ACID 事务的关系型数据库: 相应的,TCC 模式不依赖于底层数据资源的事务支持: 简单点概括,SEATA的TCC模式就是手工的AT模式,它允许你自定义两阶段的处理逻辑而不依赖AT模式的undo_log。 4. Seata TCC模式接口如何改造 假设现有一个业务需要同时使用服务 A 和服务 B 完成一个事务操作,我们在服务 A 定义该服务的一个 TCC 接口: 同样,在服务 B 定义该服务的一个 TCC 接口: 在业务所在系统中开启全局事务并执行服务 A 和服务 B 的 TCC 预留资源方法: 以上就是使用 Seata TCC 模式实现一个全局事务的例子,TCC 模式同样使用 @GlobalTransactional 注解开启全局事务,而服务 A 和服务 B 的 TCC 接口为事务参与者,Seata 会把一个 TCC 接口当成一个 Resource,也叫 TCC Resource。 5. TCC如何控制异常 在 TCC 模型执行的过程中,还可能会出现各种异常,其中最为常见的有空回滚、幂等、悬挂等。TCC 模式是分布式事务中非常重要的事务模式,但是幂等、悬挂和空回滚一直是 TCC 模式需要考虑的问题,Seata 框架在 1.5.1 版本完美解决了这些问题。 如何处理空回滚 空回滚指的是在一个分布式事务中,在没有调用参与方的 Try 方法的情况下,TM 驱动二阶段回滚调用了参与方的 Cancel 方法。 那么空回滚是如何产生的呢? 如上图所示,全局事务开启后,参与者 A 分支注册完成之后会执行参与者一阶段 RPC 方法,如果此时参与者 A 所在的机器发生宕机,网络异常,都会造成 RPC 调用失败,即参与者 A 一阶段方法未成功执行,但是此时全局事务已经开启,Seata 必须要推进到终态,在全局事务回滚时会调用参与者 A 的 Cancel 方法,从而造成空回滚。 要想防止空回滚,那么必须在 Cancel 方法中识别这是一个空回滚,Seata 是如何做的呢? Seata 的做法是新增一个 TCC 事务控制表,包含事务的 XID 和 BranchID 信息,在 Try 方法执行时插入一条记录,表示一阶段执行了,执行 Cancel 方法时读取这条记录,如果记录不存在,说明 Try 方法没有执行。 如何处理幂等 幂等问题指的是 TC 重复进行二阶段提交,因此 Confirm/Cancel 接口需要支持幂等处理,即不会产生资源重复提交或者重复释放。 那么幂等问题是如何产生的呢? 如上图所示,参与者 A 执行完二阶段之后,由于网络抖动或者宕机问题,会造成 TC 收不到参与者 A 执行二阶段的返回结果,TC 会重复发起调用,直到二阶段执行结果成功。 Seata 是如何处理幂等问题的呢? 同样的也是在 TCC 事务控制表中增加一个记录状态的字段 status,该字段有 3 个值,分别为: 二阶段 Confirm/Cancel 方法执行后,将状态改为 committed 或 rollbacked 状态。当重复调用二阶段 Confirm/Cancel 方法时,判断事务状态即可解决幂等问题。 如何处理悬挂 悬挂指的是二阶段 Cancel 方法比 一阶段 Try 方法优先执行,由于允许空回滚的原因,在执行完二阶段 Cancel 方法之后直接空回滚返回成功,此时全局事务已结束,但是由于 Try 方法随后执行,这就会造成一阶段 Try 方法预留的资源永远无法提交和释放了。 那么悬挂是如何产生的呢? 如上图所示,在执行参与者 A 的一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务有超时限制,执行 Try 方法超时后,TM 决议全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 A,执行 Try 方法进行资源预留,从而造成悬挂。 Seata 是怎么处理悬挂的呢? 在 TCC 事务控制表记录状态的字段 status 中增加一个状态: 6. Spring Cloud Alibaba整合Seata TCC实战 业务场景 用户下单,整个业务逻辑由三个微服务构成:
1) 环境准备 2) 微服务导入seata依赖 spring-cloud-starter-alibaba-seata内部集成了seata,并实现了xid传递 3)微服务application.yml中添加seata配置 注意:请确保client与server的注册中心和配置中心namespace和group一致 4)定义TCC接口 TCC相关注解如下: TCC 幂等、悬挂和空回滚问题如何解决? TCC 模式中存在的三大问题是幂等、悬挂和空回滚。在 Seata1.5.1 版本中,增加了一张事务控制表,表名是 tcc_fence_log 来解决这个问题。而在@TwoPhaseBusinessAction 注解中提到的属性 useTCCFence 就是来指定是否开启这个机制,这个属性值默认是 false。 5)微服务增加tcc_fence_log日志表
6)TCC接口的业务实现 参考课堂代码 7) 在全局事务发起者中添加@GlobalTransactional注解 核心代码 8)测试分布式事务是否生效
public interface TccActionOne {
@TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}
public interface TccActionTwo {
@TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
public void prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "b") String b);
public void commit(BusinessActionContext actionContext);
public void rollback(BusinessActionContext actionContext);
}
@GlobalTransactional
public String doTransactionCommit(){
//服务A事务参与者
tccActionOne.prepare(null,"one");
//服务B事务参与者
tccActionTwo.prepare(null,"two");
}
</ul>
<pre class="code-snippet__js" data-lang=""><code><span leaf=""><span class="code-snippet__plaintext">tried:1</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">committed:2</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">rollbacked:3</span></span></code></pre>
</ul>
<pre class="code-snippet__js" data-lang=""><code><span leaf=""><span class="code-snippet__plaintext">suspended:4</span></span></code></pre>
</section>
<section>
<span leaf=""><br></span>
</section>
<p><span leaf="">当执行二阶段 Cancel 方法时,如果发现 TCC 事务控制表没有相关记录,说明二阶段 Cancel 方法优先一阶段 Try 方法执行,因此插入一条 status=4 状态的记录,当一阶段 Try 方法后面执行时,判断 status=4 ,则说明有二阶段 Cancel 已执行,并返回 false 以阻止一阶段 Try 方法执行成功。</span></p>
</ul>
<pre class="code-snippet__js" data-lang="xml"><code><span leaf=""><span class="code-snippet__comment"><!-- seata--></span></span></code><code><span leaf=""><span class="code-snippet__tag"><span class="code-snippet__plaintext"><</span></span><span class="code-snippet__tag"><span class="code-snippet__name">dependency</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">></span></span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__tag"><span class="code-snippet__plaintext"><</span></span><span class="code-snippet__tag"><span class="code-snippet__name">groupId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">></span></span><span class="code-snippet__plaintext">com.alibaba.cloud</span><span class="code-snippet__tag"><span class="code-snippet__plaintext"><!--/</span--></span><span class="code-snippet__tag"><span class="code-snippet__name">groupId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">></span></span></span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__tag"><span class="code-snippet__plaintext"><</span></span><span class="code-snippet__tag"><span class="code-snippet__name">artifactId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">></span></span><span class="code-snippet__plaintext">spring-cloud-starter-alibaba-seata</span><span class="code-snippet__tag"><span class="code-snippet__plaintext"><!--/</span--></span><span class="code-snippet__tag"><span class="code-snippet__name">artifactId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">></span></span></span></span></code><code><span leaf=""><span class="code-snippet__tag"><span class="code-snippet__plaintext"><!--/</span--></span><span class="code-snippet__tag"><span class="code-snippet__name">dependency</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">></span></span></span></span></code></pre>
</ul>
<pre class="code-snippet__js" data-lang="bash"><code><span leaf=""><span class="code-snippet__plaintext">seata:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> application-id: </span><span class="code-snippet__variable">${spring.application.name}</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment"># seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> tx-service-group: default_tx_group</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> registry:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> # 指定nacos作为注册中心</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> type: nacos</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> nacos:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> application: seata-server</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> server-addr: 127.0.0.1:8848</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> namespace:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> group: SEATA_GROUP</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> config:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> # 指定nacos作为配置中心</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> type: nacos</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> nacos:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> server-addr: 127.0.0.1:8848</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> namespace: 7e838c12-8554-4231-82d5-6d93573ddf32</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> group: SEATA_GROUP</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> data-id: seataServer.properties</span></span></code></pre>
</ul>
<pre class="code-snippet__js" data-lang="php"><code><span leaf=""><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@author</span><span class="code-snippet__plaintext"> Fox</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 通过 </span><span class="code-snippet__doctag">@LocalTCC</span><span class="code-snippet__plaintext"> 这个注解,RM 初始化的时候会向 TC 注册一个分支事务。</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">@LocalTCC</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__class"><span class="code-snippet__keyword">interface</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext"> </span></span><span class="code-snippet__class"><span class="code-snippet__title">OrderService</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext"> </span></span><span class="code-snippet__plaintext">{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * TCC的try方法:保存订单信息,状态为支付中</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 定义两阶段提交,在try阶段通过</span><span class="code-snippet__doctag">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">注解定义了分支事务的 resourceId,commit和 cancel 方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * name = 该tcc的bean名称,全局唯一</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * commitMethod = commit 为二阶段确认方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * rollbackMethod = rollback 为二阶段取消方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * BusinessActionContextParameter注解 传递参数到二阶段中</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * useTCCFence seata1.5.1的新特性,用于解决TCC幂等,悬挂,空回滚问题,需增加日志表tcc_fence_log</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> @</span><span class="code-snippet__title">TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name = </span><span class="code-snippet__string">"prepareSaveOrder"</span><span class="code-snippet__plaintext">, commitMethod = </span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod = </span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">, useTCCFence = </span><span class="code-snippet__literal">true</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__title">Order prepareSaveOrder</span><span class="code-snippet__plaintext">(OrderVo orderVo, @</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName = </span><span class="code-snippet__string">"orderId"</span><span class="code-snippet__plaintext">) Long orderId);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * TCC的confirm方法:订单状态改为支付成功</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 二阶段确认方法可以另命名,但要保证与commitMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * context可以传递try方法的参数</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> commit</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * TCC的cancel方法:订单状态改为支付失败</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 二阶段取消方法可以另命名,但要保证与rollbackMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> rollback</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code><code><span leaf=""><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@author</span><span class="code-snippet__plaintext"> Fox</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 通过 </span><span class="code-snippet__doctag">@LocalTCC</span><span class="code-snippet__plaintext"> 这个注解,RM 初始化的时候会向 TC 注册一个分支事务。</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">@LocalTCC</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__class"><span class="code-snippet__keyword">interface</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext"> </span></span><span class="code-snippet__class"><span class="code-snippet__title">StorageService</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext"> </span></span><span class="code-snippet__plaintext">{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * Try: 库存-扣减数量,冻结库存+扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 定义两阶段提交,在try阶段通过</span><span class="code-snippet__doctag">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">注解定义了分支事务的 resourceId,commit和 cancel 方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * name = 该tcc的bean名称,全局唯一</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * commitMethod = commit 为二阶段确认方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * rollbackMethod = rollback 为二阶段取消方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * BusinessActionContextParameter注解 传递参数到二阶段中</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> commodityCode 商品编号</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> count 扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> @</span><span class="code-snippet__title">TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name = </span><span class="code-snippet__string">"deduct"</span><span class="code-snippet__plaintext">, commitMethod = </span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod = </span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">, useTCCFence = </span><span class="code-snippet__literal">true</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> deduct</span><span class="code-snippet__plaintext">(@</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName = </span><span class="code-snippet__string">"commodityCode"</span><span class="code-snippet__plaintext">) String commodityCode,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> @</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName = </span><span class="code-snippet__string">"count"</span><span class="code-snippet__plaintext">) </span><span class="code-snippet__keyword">int</span><span class="code-snippet__plaintext"> count);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * Confirm: 冻结库存-扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 二阶段确认方法可以另命名,但要保证与commitMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * context可以传递try方法的参数</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> commit</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * Cancel: 库存+扣减数量,冻结库存-扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 二阶段取消方法可以另命名,但要保证与rollbackMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> rollback</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code><code><span leaf=""><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@author</span><span class="code-snippet__plaintext"> Fox</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 通过 </span><span class="code-snippet__doctag">@LocalTCC</span><span class="code-snippet__plaintext"> 这个注解,RM 初始化的时候会向 TC 注册一个分支事务。</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">@LocalTCC</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__class"><span class="code-snippet__keyword">interface</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext"> </span></span><span class="code-snippet__class"><span class="code-snippet__title">AccountService</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext"> </span></span><span class="code-snippet__plaintext">{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 用户账户扣款</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 定义两阶段提交,在try阶段通过</span><span class="code-snippet__doctag">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">注解定义了分支事务的 resourceId,commit和 cancel 方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * name = 该tcc的bean名称,全局唯一</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * commitMethod = commit 为二阶段确认方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * rollbackMethod = rollback 为二阶段取消方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> userId</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> money 从用户账户中扣除的金额</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> @</span><span class="code-snippet__title">TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name = </span><span class="code-snippet__string">"debit"</span><span class="code-snippet__plaintext">, commitMethod = </span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod = </span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">, useTCCFence = </span><span class="code-snippet__literal">true</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> debit</span><span class="code-snippet__plaintext">(@</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName = </span><span class="code-snippet__string">"userId"</span><span class="code-snippet__plaintext">) String userId,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> @</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName = </span><span class="code-snippet__string">"money"</span><span class="code-snippet__plaintext">) </span><span class="code-snippet__keyword">int</span><span class="code-snippet__plaintext"> money);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 提交事务,二阶段确认方法可以另命名,但要保证与commitMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * context可以传递try方法的参数</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> commit</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * 回滚事务,二阶段取消方法可以另命名,但要保证与rollbackMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> *</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext"> actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> * </span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> */</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title"> rollback</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code></pre>
</section>
<section>
<span leaf=""><br></span>
</section>
</ul>
<pre class="code-snippet__js" data-lang="sql"><code><span leaf=""><span class="code-snippet__plaintext"># tcc_fence_log 建表语句如下(MySQL 语法)</span></span></code><code><span leaf=""><span class="code-snippet__keyword">CREATE</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">TABLE</span><span class="code-snippet__plaintext"> IF </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">EXISTS</span><span class="code-snippet__plaintext"> `tcc_fence_log`</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">(</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> `xid` </span><span class="code-snippet__type">VARCHAR</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__number">128</span><span class="code-snippet__plaintext">) </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext"> COMMENT </span><span class="code-snippet__string">'global id'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> `branch_id` </span><span class="code-snippet__type">BIGINT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext"> COMMENT </span><span class="code-snippet__string">'branch id'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> `action_name` </span><span class="code-snippet__type">VARCHAR</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__number">64</span><span class="code-snippet__plaintext">) </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext"> COMMENT </span><span class="code-snippet__string">'action name'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> `status` TINYINT </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext"> COMMENT </span><span class="code-snippet__string">'status(tried:1;committed:2;rollbacked:3;suspended:4)'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> `gmt_create` DATETIME(</span><span class="code-snippet__number">3</span><span class="code-snippet__plaintext">) </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext"> COMMENT </span><span class="code-snippet__string">'create time'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> `gmt_modified` DATETIME(</span><span class="code-snippet__number">3</span><span class="code-snippet__plaintext">) </span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext"> COMMENT </span><span class="code-snippet__string">'update time'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> </span><span class="code-snippet__keyword">PRIMARY</span><span class="code-snippet__plaintext"> KEY (`xid`, `branch_id`),</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> KEY `idx_gmt_modified` (`gmt_modified`),</span></span></code><code><span leaf=""><span class="code-snippet__plaintext"> KEY `idx_status` (`status`)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">) ENGINE </span><span class="code-snippet__operator">=</span><span class="code-snippet__plaintext"> InnoDB</span></span></code><code><span leaf=""><span class="code-snippet__keyword">DEFAULT</span><span class="code-snippet__plaintext"> CHARSET </span><span class="code-snippet__operator">=</span><span class="code-snippet__plaintext"> utf8mb4;</span></span></code></pre>
@GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
public Order saveOrder(OrderVo orderVo) {
log.info("=============用户下单=================");
log.info("当前 XID: {}", RootContext.getXID());
//获取全局唯一订单号 测试使用
Long orderId = UUIDGenerator.generateUUID();
//阶段一: 创建订单
Order order = orderService.prepareSaveOrder(orderVo,orderId);
//扣减库存
storageFeignService.deduct(orderVo.getCommodityCode(), orderVo.getCount());
//扣减余额
accountFeignService.debit(orderVo.getUserId(), orderVo.getMoney());
return order;
}