TCC Demo 代码实现

本文通过一个TCC Demo展示了如何设计一个简单的TCC事务管理器,包括TCC的Try、Confirm、Cancel阶段,以及如何通过注解进行事务拦截和处理。文中还介绍了TCC的原理,并给出了不使用事务管理器时的伪代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TCC Demo 代码实现


简介

    设计实现一个 TCC 分布式事务框架的简单 Demo,实现事务管理器,不需要实现全局事务的持久化和恢复、高可用等

工程运行

    需要MySQL数据库,保存全局事务信息,相关TCC步骤都会打印在控制台上

  • 1:启动MySQL,创建一个数据库 test
  • 2.运行当前工程的:TccDemoApplication,启动以后自动创建数据库的表
  • 3.访问:http://localhost:8080/transaction/commit,confirm示例
  • 4.访问:http://localhost:8080/transaction/cancel,cancel示例

大致实现思路

  • 1.初始化:想事务管理器注册新事务,生成全局事务唯一ID
  • 2.try阶段执行:try相关的代码执行,期间注册相应的调用记录,发送try执行结果到事务管理器,执行成功由事务管理器执行confirm或者cancel步骤
  • 3.confirm阶段:事务管理器收到try执行成功信息,根据事务ID,进入事务confirm阶段执行,confirm失败进入cancel,成功则结束
  • 4.cancel阶段:事务管理器收到try执行失败或者confirm执行失败,根据事务ID,进入cancel阶段执行后结束,如果失败了,打印日志或者告警,让人工参与处理

前置知识

TCC 原理

    TCC分布式事务主要的三个阶段:

  • 1.Try:主要是对业务系统做检测及资源预留
  • 2.Confirm:确认执行业务操作
  • 3.Cancel:取消执行业务操作

    下面以一个例子来说明三个阶段需要做的事:比如现在有两个数据库,一个用户账户数据库、一个商品库存数据库,现在提供一个买货的接口,当买卖成功时,扣除用户账户和商品库存,大致伪代码如下:

public void buy() {
   
   
    // 用户账户操作
    userAccount();
    // 商品账户操作
    StoreAccount();
}

    在上面这个操作做,两个函数的操作必须同时成功,不然就会出现数据不一致问题,也就是需要保证事务原子性。

    因为设定的场景是数据在两个不同的数据库,所有没有办法利用单个数据库的事务机制,它是跨数据库的,所以需要分布式事务的机制。

    下面简单模拟下,在不使用TCC事务管理器,按照TCC的思想,在代码中如何保证事务原子性

TCC 无事务管理器 Demo 伪代码

    使用上面的场景,代码大致如下:

class Demo {
   
   
    
    public void buy() {
   
   
        // try 阶段:比如去判断用户和商品的余额和存款是否充足,进行预扣款和预减库存
        if (!userServer.tryDeductAccount()) {
   
   
            // 用户预扣款失败,相关数据没有改变,返回错误即可
        }
        if (!storeService.tryDeductAccount()) {
   
   
            // cancel 阶段: 商品预减库存失败,因为前面进行了用户预扣款,所以需要进入cancel阶段,恢复用户账户
            userService.cancelDeductAccount();
        }

        // Confirm 阶段:try 成功就进行confirm阶段,这部分操作比如是将扣款成功状态和减库存状态设置为完成
        if (!userService.confirmDeductAccount() || !storeService.confirmDeductAccount()) {
   
   
            // cancel 阶段:confirm的任意阶段失败了,需要进行数据恢复(回滚)
            userService.cancelDeductAccount();
            storeService.cancelDeductAccount();
        }
    }
}

    上面就是一个TCC事务大致代码,可以看到:之前的每个函数操作都需要分为三个子函数,try、confirm、cancel。将其细化,在代码中判断执行,保证其事务原子性。

    上面是两个服务,用户账户和商品存储操作,看着写起来不是太多,但如果是多个服务呢?try阶段就会多很多的if,还有相应的cancel的动态增加,confirm也是,大致如下:

class Demo {
   
   
    
    public void buy() {
   
   
        // try 阶段:比如去判断用户和商品的余额和存款是否充足,进行预扣款和预减库存
        if (!userServer.tryDeductAccount()) {
   
   
            // 用户预扣款失败,相关数据没有改变,返回错误即可
        }
        if (!storeService.tryDeductAccount()) {
   
   
            // cancel 阶段: 商品预减库存失败,因为前面进行了用户预扣款,所以需要进入cancel阶段,恢复用户账户
            userService.cancelDeductAccount();
        }
        // try增加、cancel也动态增加
        if (!xxxService.tryDeductAccount()) {
   
   
            xxxService.cancelDeductAccount();
            xxxService.cancelDeductAccount();
        }
        if (!xxxService.tryDeductAccount()) {
   
   
            xxxService.cancelDeductAccount();
            xxxService.cancelDeductAccount();
            xxxService.cancelDeductAccount();
        }
        ........

        // Confirm 阶段:try 成功就进行confirm阶段,这部分操作比如是将扣款成功状态和减库存状态设置为完成
        if (!userService.confirmDeductAccount() || !storeService.confirmDeductAccount() || ......) {
   
   
            // cancel 阶段:confirm的任意阶段失败了,需要进行数据恢复(回滚)
            userService.cancelDeductAccount();
            storeService.cancelDeductAccount();
            .......
        }
    }
}

    可以看出代码相似性很多,工程中相似的需要分布式调用的有很多,这样的话,大量这样的类似代码就会充斥在工程中,为了偷懒,引入TCC事务管理器就能简化很多

TCC 事务管理器

    为了偷懒,用事务管理器,那偷的是哪部分懒呢?在之前的代码中,try阶段还是交给本地程序去做,而confirm和cancel委托给了事务管理器。下面看下Seata和Hmily的TCC伪代码:

interface UserService {
   
   

    @TCCAction(name = "userAccount", confirmMethod = "confirm", cancelMethod = "cancel")
    public void try();

    public void confirm();

    public void cancel();
}

interface StoreService {
   
   

    @TCCAction(name 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值