分布式事务-TCC

TCC事务介绍

TCC(Try-Confirm-Cancel)是除可靠消息队列以外的另一种常见的分布式事务机制,它是由数据库专家帕特 · 赫兰德(Pat Helland)在2007年撰写的论文《Life beyond Distributed Transactions: An Apostate's Opinion》中提出的。正式以Try-Confirm-Cancel作为名称的是Atomikos公司,其注册了TCC商标。

Atomikos公司在商业版本事务管理器ExtremeTransactions中提供了TCC方案的实现,但是由于其是收费的,因此相应的很多的开源实现方案也就涌现出来,如:tcc-transactionByteTCChmilyspring-cloud-rest-tcc

分布式事务-rocketmq半消息事务,虽然它也能保证最终的结果是相对可靠的,过程也足够简单(相对于TCC来说),但可靠消息队列的整个实现过程完全没有任何隔离性可言。

如果业务需要隔离,我们通常就应该重点考虑TCC方案,它天生适合用于需要强隔离性的分布式事务中

在具体实现上,TCC的操作有点复杂,它是一种业务侵入性较强的事务方案,要求业务处理过程必须拆分为"预留业务资源"和"确认/释放消费资源"两个子过程。另外,你看名字也能看出来,TCC的实现过程分为了三个阶段:

  • Try: 尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好事务需要用到的所有业务资源(保障隔离性)。
  • Confirm: 确认执行阶段,不进行任何业务检查,直接使用Try阶段准备的资源来完成业务处理。注意,Confirm阶段可能会重复执行,因此需要满足幂等性。
  • Cancel: 取消执行阶段,释放Try阶段预留的业务资源。注意,Cancel阶段也可能会重复执行,因此也需要满足幂等性。

空回滚和业务悬挂问题

空回滚 :当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚

业务悬挂 :对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。应当阻止执行空回滚后的try操作,避免悬挂。

TCC与XA区别

之前分布式事务-两阶段、三阶段提交介绍了二阶段提交,TCC与XA两阶段提交有着异曲同工之妙。下面我们看下两者的区别:

  • 执行阶段上:

    • 阶段1: 在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);而在TCC中,是主业务活动请求(try)各个从业务服务预留资源。

    • 阶段2: XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。

  • XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁

    XA事务中的两阶段提交内部过程是对开发者屏蔽的,JTA规范中,通过UserTransactioncommit方法来提交全局事务,这只是一次方法调用,其内部会委派给TransactionManager进行真正的两阶段提交,因此开发者从代码层面是感知不到这个过程的。而事务管理器在两阶段提交过程中,从preparecommit/rollback过程中,资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录,那么就必须等待锁释放。

  • TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁

    TCC中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。如下单案例中:在第一阶段,库存中心需要提供try接口(库存预留)。在第二阶段,库存需要提供confirm/cancel接口(确认库存扣减/取消库存预扣减)。开发者明显的感知到了两阶段提交过程的存在。try、confirm/cancel在执行过程中,一般都会开启各自的本地事务,来保证方法内部业务逻辑的ACID特性。其中:

    1. try过程的本地事务,是保证资源预留的业务逻辑的正确性。
    2. confirm/cancel执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务(Compensation-Based Transactions)。

示例

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:资金服务、红包服务、订单服务。

红包服务

Java 复制代码
public interface RedPacketTradeOrderService {
    @EnableTcc
    public String record(RedPacketTradeOrderDto tradeOrderDto);
}

资金服务

Java 复制代码
public interface CapitalTradeOrderService {
    @EnableTcc
    public String record(CapitalTradeOrderDto tradeOrderDto);
}

订单服务

主要业务逻辑如下:

Java 复制代码
@Service
public class PaymentServiceImpl {
    @Autowired
    CapitalTradeOrderService capitalTradeOrderService;
    @Autowired
    RedPacketTradeOrderService redPacketTradeOrderService;
    @Autowired
    OrderRepository orderRepository;
    @Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment", asyncConfirm = false)
    public void makePayment(@UniqueIdentity String orderNo) {
        System.out.println("order try make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
        Order order = orderRepository.findByMerchantOrderNo(orderNo);
        String result = capitalTradeOrderService.record(buildCapitalTradeOrderDto(order));
        String result2 = redPacketTradeOrderService.record(buildRedPacketTradeOrderDto(order));
    }
    public void confirmMakePayment(String orderNo) {
        System.out.println("order confirm make payment called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
        Order foundOrder = orderRepository.findByMerchantOrderNo(orderNo);
        //check if the trade order status is PAYING, if no, means another call confirmMakePayment happened, return directly, ensure idempotency.
        if (foundOrder != null) {
            foundOrder.confirm();
            orderRepository.update(foundOrder);
        }
    }
    public void cancelMakePayment(String orderNo) {
        System.out.println("order cancel make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
        Order foundOrder = orderRepository.findByMerchantOrderNo(orderNo);
        //check if the trade order status is PAYING, if no, means another call cancelMakePayment happened, return directly, ensure idempotency.
        if (foundOrder != null) {
            foundOrder.cancelPayment();
            orderRepository.update(foundOrder);
        }
    }
}

相关资料

开源TCC实现方案

Atomikos的官方TCC参考文档

相关推荐
幼稚园的山代王2 分钟前
RabbitMQ 4.1.1初体验
分布式·rabbitmq·ruby
蓝澈11213 分钟前
迪杰斯特拉算法之解决单源最短路径问题
java·数据结构
百锦再5 分钟前
RabbitMQ用法的6种核心模式全面解析
分布式·rabbitmq·路由·消息·通道·交换机·代理
Kali_0710 分钟前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
一路向北North13 分钟前
RabbitMQ简单消息监听和确认
分布式·rabbitmq·ruby
rzl0222 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习27 分钟前
RocketMQ延迟消息是如何实现的?
后端
guojl41 分钟前
深度解读jdk8 HashMap设计与源码
java
Falling421 小时前
使用 CNB 构建并部署maven项目
后端
guojl1 小时前
深度解读jdk8 ConcurrentHashMap设计与源码
java