分布式事务之TCC

本文深入探讨了分布式事务的概念,强调了其AICD特性,并重点介绍了TCC(Try-Confirm-Cancel)补偿事务机制。TCC通过预操作、确认提交和业务取消确保事务的一致性。在电商系统示例中,详细展示了TCC如何在订单、资金账户和红包账户服务间协调事务。当异常发生时,系统会通过Cancel回滚操作。此外,还提供了TCC框架的代码实现以及系统搭建与测试步骤,展示了正常和异常情况下的执行顺序。
摘要由CSDN通过智能技术生成

一、定义

分布式事务:分布式事务指事务的操作位于不同的节点上,需要保证事务的 AICD 特性,一个系统涉及到多个业务系统,出错时需要全部回滚,一般采取两阶段提交(2PC)、补偿事务(TCC)、MQ事务消息

还可以使用补单操作,来完成任务的

图1-1  电商系统订单服务涉及到库存、积分、仓储服务的数据库,出现问题时,无法直接对它们进行回滚

 

二、TCC补偿分布式事务

1.TCC作用机制

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

  • Try 阶段主要是对业务系统做检测及资源预留

  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。

  • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 电商系统 要扣除资金账户系统金额,电商系统和资金账户系统都要做tcc操作

1)首先在 Try 阶段,扣除资金账户余额,记录变更的扣除金额,并更改状态为支付中

2)在 Confirm 阶段,正式扣资金账户余额,更改订单状态为已支付,将扣除金额置0

3)如果第2步执行成功,那么转账成功,如果第1步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel),资金账户余额加上变更的扣除金额,更改状态为 支付失败

2.TCC执行顺序

执行顺序:try->confirm或try-cancel

正常情况的执行顺序如下所示:

若第三方服务try出现了异常,需要进行全部回滚,执行各个服务的cancel方法,异常情况执行顺序:

若是在confirm和cancel中出现了异常(机器宕机、超时等),TCC 事务框架会通过活动日志记录各个服务的状态,发现某个服务的 Cancel 或者 Confirm 一直没成功,会不停的重试调用它的 Cancel 或者 Confirm 逻辑,务必要它成功

缺点:需要各个业务方都内嵌tcc事务框架,协调起来麻烦,复用性低

3.TCC框架:tcc-transaction框架核心代码

电商系统为服务调用者consumer

资金账户系统、红包账户系统为provider

1)电商系统下单

@Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment", asyncConfirm = false, delayCancelExceptions = {SocketTimeoutException.class, org.apache.dubbo.remoting.TimeoutException.class})
public void makePayment(@UniqueIdentity String orderNo, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
    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);
    //check if the order status is DRAFT, if no, means that another call makePayment for the same order happened, ignore this call makePayment.
    //更改商品状态为paying
    if (order.getStatus().equals("DRAFT")) {
        order.pay(redPacketPayAmount, capitalPayAmount);
        try {
            orderRepository.updateOrder(order);
        } catch (OptimisticLockingFailureException e) {
            //ignore the concurrently update order exception, ensure idempotency.
        }
    }

    String result = capitalTradeOrderService.record(buildCapitalTradeOrderDto(order));
    String result2 = redPacketTradeOrderService.record(buildRedPacketTradeOrderDto(order));
}

2)资金账户下单-Try阶段

@Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = DubboTransactionContextEditor.class)
@Transactional
public String record(CapitalTradeOrderDto tradeOrderDto) {
    TradeOrder foundTradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());
    //check if trade order has been recorded, if yes, return success directly.商品订单不存在,新增
    if (foundTradeOrder == null) {
        TradeOrder tradeOrder = new TradeOrder(
                tradeOrderDto.getSelfUserId(),
                tradeOrderDto.getOppositeUserId(),
                tradeOrderDto.getMerchantOrderNo(),
                tradeOrderDto.getAmount()
        );
        try {
            //资金账户插入一笔交易商品订单
            tradeOrderRepository.insert(tradeOrder);
            //根据用户id查询用户对应的资金账户信息
            CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());
            //扣减用户资金账户余额
            transferFromAccount.transferFrom(tradeOrderDto.getAmount());
            //更新用户资金账户余额信息,更新余额
            capitalAccountRepository.save(transferFromAccount);
        } catch (DataIntegrityViolationException e) {
            //this exception may happen when insert trade order concurrently, if happened, ignore this insert operation.
        }
    }
    return "success";
}

3)资金账户确认阶段-Confirm

@Transactional(rollbackFor = Exception.class)
public void confirmRecord(CapitalTradeOrderDto tradeOrderDto) {

    //根据商品订单号查询一笔交易商品订单
    TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());
    //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency.
    //检查资金账户交易订单是否为try的DRAFT状态
    if (tradeOrder != null && tradeOrder.getStatus().equals("DRAFT")) {
        //更改商品订单状态为CONFIRM
        tradeOrder.confirm();
        tradeOrderRepository.update(tradeOrder);
        //根据用户id查询用户对应的资金账户信息
        CapitalAccount transferToAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getOppositeUserId());
        
        //增加用户账户资金余额
        //这里应该是要正式扣除,变更状态后,需要将扣除金额的字段置0,然后余额不做变化,此资金账户系统中没有变更金额字段,
        // 因此最后资金又返还了,交易过程中资金流向不会出现幂等,正式场景要修改下面两步操作
        transferToAccount.transferTo(tradeOrderDto.getAmount());
        //更新用户资金账户余额信息,更新余额
        capitalAccountRepository.save(transferToAccount);
    }
}

4)资金账户取消阶段-Cancel

@Transactional(rollbackFor = Exception.class)
public void cancelRecord(CapitalTradeOrderDto tradeOrderDto) {

    TradeOrder tradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());

    //check if the trade order status is DRAFT, if yes, return directly, ensure idempotency.
    //订单状态为DRAFT,更改为CANCEL状态
    if (null != tradeOrder && "DRAFT".equals(tradeOrder.getStatus())) {
        //更改商品订单状态为CANCEL
        tradeOrder.cancel();
        tradeOrderRepository.update(tradeOrder);

        //根据用户id查询用户对应的资金账户信息
        CapitalAccount capitalAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());

        //增加用户账户资金余额
        capitalAccount.cancelTransfer(tradeOrderDto.getAmount());
        
        //更新用户资金账户余额信息,更新余额
        capitalAccountRepository.save(capitalAccount);
    }
}

红包账户服务同2)、3)、4)

5)电商系统账户阶段-Confirm

public void confirmMakePayment(String orderNo, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
    Order foundOrder = orderRepository.findByMerchantOrderNo(orderNo);

    //check if the trade order status is PAYING, if no, means another call confirmMakePayment happened, return directly, ensure idempotency.
    //Confirm阶段:更该商品状态为CONFIRMED
    if (foundOrder != null && foundOrder.getStatus().equals("PAYING")) {
        foundOrder.confirm();
        orderRepository.updateOrder(foundOrder);
    }
}

6)电商系统账户阶段-Cancel

public void cancelMakePayment(String orderNo,  BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
    Order foundOrder = orderRepository.findByMerchantOrderNo(orderNo);

    //check if the trade order status is PAYING, if no, means another call cancelMakePayment happened, return directly, ensure idempotency.
    //Concel阶段:更改商品状态为PAY_FAILED
    if (foundOrder != null && foundOrder.getStatus().equals("PAYING")) {
        foundOrder.cancelPayment();
        orderRepository.updateOrder(foundOrder);
    }
}

4.系统搭建测试

详情请看:https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x

这里使用http调用方式,

前提条件:导入依赖、更改数据库连接配置、生成sql脚本文件、变更配置中心、war包部署

注意:提供的http接口号与配置的tomacat端口号需要不一致,否则会出现地址被占用情况,打包的时http的war包

1)启动三个服务

2)进入order电商系统主界面

3)点击商品列表购买商品

4)正常购买各个系统会打印4.2的日志信息

这里测试正常和异常情况,正常情况输入红包金额小于红包可用余额或者购买的商品价格小于可用账户余额即可,

异常情况测试红包金额大于可用红包账户系统余额的情况

5)异常情况说明

红包账户系统前两次正常购买正常,第三次出现异常,最后执行了cancel方法进行回滚操作

资金账户系统前两次购买正常,后一次由于红包系统出现了异常,try后调用cancel方法,进行回滚

电商系统账户前两次购买正常,后一次由于红包系统出现了异常,本身也会出现异常,之后执行cancel方法

可以看到,执行顺序为:

正常情况下:order try->capital try->redpacket try->order  confirm->capital  confirm->redpacket  confirm

异常情况(redpacket):order try->capital try->redpacket try->redpacket异常->order  concel->capital  concel->redpacket  concel

TCC(Try-Confirm-Cancel)是一种分布式事务解决方案,它通过将一个事务拆分成三个阶段(Try、Confirm、Cancel)来保证事务的一致性。而在分布式环境下,TCC通常使用3PC协议来实现。 3PC(Three-Phase Commit)协议是一种常用的分布式事务协议,它将一个分布式事务分为三个阶段(CanCommit、PreCommit、DoCommit),并通过协调者(Coordinator)和参与者(Participant)之间的消息交换来实现事务的原子性和一致性。 具体来说,TCC使用的3PC协议流程如下: 1. CanCommit 阶段:协调者向参与者发送CanCommit请求,询问参与者是否可以执行该事务。如果参与者可以执行,则发送Yes响应,否则发送No响应。 2. PreCommit 阶段:协调者向参与者发送PreCommit请求,告诉参与者可以执行该事务,并让它准备好提交或撤销事务。如果参与者准备好了,则发送Ack响应。 3. DoCommit 阶段:协调者向参与者发送DoCommit请求,告诉参与者提交该事务。如果参与者成功提交了事务,则发送Ack响应,否则发送Abort响应。 如果任何一个阶段出现问题,协调者会向参与者发送Cancel请求,撤销事务。 需要注意的是,在TCC中,Try阶段和Cancel阶段由应用程序自己来实现,3PC协议主要用于Confirm和DoCommit阶段的协调。此外,由于3PC协议的复杂性和性能问题,TCC并不适用于所有的分布式事务场景,开发者需要根据具体的业务需求来选择合适的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值