如何在业务中体现TCC事务模型
在分布式系统设计中,随着微服务的流行,通常一个业务操作被拆分为多个子任务,比如电商系统的下单和支付操作,就涉及到了创建和更新订单、扣减账户余额、扣减库存、发送物流消息等,那么在复杂业务开发中,如何保证最终数据一致性呢?
TCC事务模型是什么
TCC(Try-Confirm-Cancel)的概念来源于Pat Helland 发表的一篇名为“Life beyond Distributed Transaction an Apostate’s Opinion”的论文。
TCC提出了一种新的事务模型,基于业务层面的事务定义,锁粒度完全由业务自己控制,目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题。TCC把事务运行过程分成了Try、Confirm/ Cancel两个阶段,每个阶段的逻辑由业务代码控制,避免了长事务,可以获取更高的性能。
TCC的各个阶段
TCC的具体流程如下所示
Try阶段:调用Try接口,尝试执行业务,完成所有业务检查,预留业务资源。
Confirm 或 Cancel阶段:两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
Confirm 操作:对业务做确认提交,确认执行业务操作,不做其他业务检查,只使用Try阶段预留的业务资源。
Cancel 操作:在业务执行出错误,需要回滚的状态下执行业务取消,释放预留资源。Try阶段失败可以Cancel,如果Confirm和Cancel阶段失败了怎么办?
TCC中会添加事务日志,如果Confirm或者Cancel阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。
从真实业务场景分析TCC
以一个电商中的支付业务来演示,用户在支付以后,需要进行更新订单状态,扣减账户余额,增加账户积分和扣减商品操作。
在实际业务中为了防止超卖,有下单减库存和付款减库存的区别,支付除了账户余额,还有各种第三方支付等,这里我们为了描述方便,统一使用扣款减库存,扣款来源是用户账户余额。
业务逻辑拆解
我们把订单业务拆解为以下几个步骤:
- 订单更新为支付完成状态
- 扣减用户账户余额
- 增加用户账户积分
- 扣减当前商品的库存
如果不使用事务,上面的几个步骤都可能出现失败,最终会导致大量的数据的不一致,比如订单状态会更新失败,扣款却成功了;或者扣款失败,库存却扣减了等情况,这个在业务上是不能接受的,会出现大量的投诉。
如果直接应用事务,不使用分布式事务,比如在代码中添加Spring的声明式事务注解,这样做实际上是在事务中嵌套了远程服务调用,一旦服务调用出现超时,事务无法提交,就会导致数据库连接被占用,出现大量的阻塞和失败,会导致服务宕机,另一方面,如果没有定义额外的回滚操作,比如遇到异常,非DB的服务调用失败时,则无法正确执行回滚。
业务系统改造
下面应用TCC事务,需要对业务代码改造,抽象Try、Confirm和Cancel阶段。
- Try操作
Try操作一般都是锁定某个资源,设置一个预备的状态,冻结部分数据,比如,订单服务添加一个预备状态,修改为UPDATING,也就是更新中的意思,冻结当前订单的操作,而不是就直接修改为支付成功。
- Confirm操作
Confirm操作就是把前边Try操作锁定的资源提交,类比数据库事务中的Commit操作,在支付场景中,包括订单状态从准备中更新为支付成功,库存数据扣减冻结库存,积分数据增加预增加积分。
- Cancel操作
Cancel操作执行的是业务上回滚操作,类比数据库事务中的Rollback操作,首先订单服务,撤销预备状态,还原为待支付状态或者已取消状态,库存服务冻结库存,添加到可销售库存中,积分服务也是一样,将预增加积分扣减掉。
执行业务操作
下面来分析业务的实际执行操作,首先业务请求过来,开始执行 Try 操作,如果 TCC 分布式事务框架感知到各个服务的 Try 阶段都成功了以后,就会执行各个服务的 Confirm 逻辑。
如果 Try 阶段有操作不能正确执行,比如订单失效、库存不足等,就会执行 Cancel 的逻辑,取消事务提交
这里放上整个支付阶段的TCC事务流程
TCC对比2PC两阶段提交
TCC事务模型的思想类似2PC提交,下面对比TCC和基于2PC事务XA规范对比
对比2PC提交
- 第一阶段
在XA事务中,各个RM准备提交各自的事务分支,事实上就是准备提交资源更新操作(insert、delete、update等);而在TCC中,是主业务操作请求各个子业务服务预留资源。
- 第二阶段
XA事务根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚,如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。
在 TCC 中,如果在第一阶段所有业务资源都预留成功,那么进入 Confirm 步骤,提交各个子业务服务,完成实际的业务处理,否则进入 Cancel 步骤,取消资源预留请求。
与2PC/XA两阶段提交的区别
2PC/XA是数据库或者存储资源层面的事务,实现的是强一致性,在两阶段提交的整个过程中,一直会持有数据库的锁。
TCC关注业务层的正确提交和回滚,在Try阶段不涉及加锁,是业务层的分布式业务,关注最终一致性,不会一直持有各个业务资源的锁
TCC的核心思想是针对每个业务操作,都要添加一个与其对应的确认和补偿操作,同时把相关的处理,从数据库转移到业务中,以此实现跨数据库的事务。