七、Seata的TCC 模式


Seata的TCC 模式

TCC事务模式

TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下:

  1. Try:对业务资源的检查并预留;
  2. Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功;
  3. Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放。

TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。

在这里插入图片描述

Seata的TCC模式

Seata TCC 模式跟通用型 TCC 模式原理一致。

TCC和AT区别

AT 模式基于 支持本地 ACID 事务关系型数据库

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

在这里插入图片描述

具体实现

实现代码

实现代码地址:https://download.csdn.net/download/m_lonel/85911509

首先我们还是使用上次实现AT模式的项目,一个订单服务seata-order-8001, 一个库存服务seata-stock-8002。(具体配置可参考专栏里面的另一篇文章——四、Seata的AT模式

  1. 首先在订单服务seata-order-8001服务中新增方法

OrderController:

@GetMapping("/createOrder-tcc")
@GlobalTransactional// 开启分布式事务
public String createTcc(){
    Map<String, String> params = new HashMap<>(1);
    params.put("name", "张三");
    orderService.createTcc(params);
    return "生成订单";
}

OrderService:

/**
 * TCC
 * @param params
 */
void createTcc(Map<String, String> params);

OrderServiceImpl:

@Override
public void createTcc(Map<String, String> params) {
    System.out.println("TCC------------------> xid = " + RootContext.getXID());

    // 减库存
    stockClient.changeStockTcc(params);

    // 添加异常, 测试时此处添加断点
    int i = 1/0;

    // 创建订单
    orderMapper.create();
}

StockClient:

@PutMapping("/changeStock-tcc")
String changeStockTcc(Map<String, String> params);
  1. 在库存服务seata-stock-8002服务中新增方法和文件

StockController:

package com.seata.seataStock8002.controller;

import com.seata.seataStock8002.service.StockService;
import com.seata.seataStock8002.service.StockServiceTcc;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Map;

@RestController
public class StockController {

    @Resource
    private StockService stockService;

    @Resource
    private StockServiceTcc stockServiceTcc;

    @PutMapping("/changeStock-at")
    public String changeStockAt() {
        stockService.changeStockAt();
        return "库存减1";
    }


    @PutMapping("/changeStock-tcc")
    public String changeStockTcc(@RequestBody Map<String, String> params) {
        stockServiceTcc.changeStockTcc(params);
        return "库存减1";
    }
}

新建StockServiceTcc文件:

package com.seata.seataStock8002.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

import java.util.Map;

/**
 * 这里定义tcc的接口
 * 一定要定义在接口上
 * 我们使用springCloud的远程调用
 * 那么这里使用LocalTCC便可
 */
@LocalTCC
public interface StockServiceTcc {

    /**
     * 定义两阶段提交
     * name = 该tcc的bean名称,全局唯一, 一般写方法名即可;
     * commitMethod = commit 为二阶段确认方法
     * rollbackMethod = rollback 为二阶段取消方法
     * BusinessActionContextParameter注解 传递参数到二阶段中
     *
     * @param params  -入参
     * @return String
     */
    @TwoPhaseBusinessAction(name = "changeStockTcc", commitMethod = "commitTcc", rollbackMethod = "cancel")
    void changeStockTcc(@BusinessActionContextParameter(paramName = "params") Map<String, String> params);

    /**
     * 确认方法、可以另命名,但要保证与commitMethod一致
     * context可以传递try方法的参数
     *
     * @param context 上下文
     * @return boolean
     */
    boolean commitTcc(BusinessActionContext context);

    /**
     * 二阶段回滚方法
     *
     * @param context 上下文
     * @return boolean
     */
    boolean cancel(BusinessActionContext context);
}

新建StockServiceTccImpl文件:

package com.seata.seataStock8002.service.impl;

import com.seata.seataStock8002.mapper.StockMapper;
import com.seata.seataStock8002.service.StockService;
import com.seata.seataStock8002.service.StockServiceTcc;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Map;

@Service
public class StockServiceTccImpl implements StockServiceTcc {

    @Resource
    private StockMapper stockMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changeStockTcc(Map<String, String> params) {
        System.out.println("changeStockTcc------------------> xid = " + RootContext.getXID());
        stockMapper.subStock();
    }


    /**
     * tcc服务 confirm方法
     * 若一阶段采用资源预留,在二阶段确认时要提交预留的资源
     *
     * @param context 上下文
     * @return boolean
     */
    @Override
    public boolean commitTcc(BusinessActionContext context) {
        System.out.println("xid = " + context.getXid() + "提交成功");
        //todo 若一阶段资源预留,这里则要提交资源
        return true;
    }

    /**
     * tcc 服务 cancel方法
     *
     * @param context 上下文
     * @return boolean
     */
    @Override
    public boolean cancel(BusinessActionContext context) {
        //todo 这里写中间件、非关系型数据库的回滚操作
        System.out.println("please manually rollback this data:" + context.getActionContext("params"));
        // 回滚
        stockMapper.subStockRollback();
        return true;
    }
}

StockMapper:

@Update("update tb_stock set count = count + 1 where goods_id = 1")
void subStockRollback();

数据库设计

还是使用两个数据库:seata-order和seata-stock

在这里插入图片描述

案例演示

前提:Nacos 和将Seata-Server为正常运行状态

  1. 首先,将服务seata-order-8001和 seata-stock-8002用Debug模式运行起来

    在这里插入图片描述

  2. 调用接口前,seata-stock库中的库存表tb_stock的库存数量count为100,seata-order库中的订单表tb_order中无订单记录

在这里插入图片描述

在这里插入图片描述

  1. 然后我们在订单服务中的OrderServiceImpl的createTcc方法中打上断点, 在库存服务中的StockServiceTccImpl的cancel方法中也打上断点

    在这里插入图片描述
    在这里插入图片描述

  2. 这个时候我们进行访问Order的REST接口:http://localhost:8001/createOrder-tcc,此时程序进了断点,我们去查看数据库

在这里插入图片描述

此时可以看到seata-stock库中库存表tb_stock的库存数量count减少了到了99

在这里插入图片描述

然后放行第一个断点,可以看到程序进了库存服务的cancel方法,放行断点,数据被回滚更新

在这里插入图片描述

此时我们查看数据库,seata-stock库中库存表tb_stock的库存数量count恢复到了100

在这里插入图片描述

seata-order库中的订单表tb_order中订单记录也没有新增,此时我们就验证了TCC事务的执行过程

在这里插入图片描述

注意

被调用方需要用到的几个注解:
@LocalTCC (必要)
该注解需要添加到接口上,表示实现该接口的类被 seata 来管理,seata 根据事务的状态,自动调用我们定义的方法,如果没问题则调用 Commit 方法,否则调用 Rollback 方法。

@TwoPhaseBusinessAction (必要)
该注解用在接口的 Try 方法上,该注解的用法如下:

@TwoPhaseBusinessAction(name = "changeStockTcc", commitMethod = "commitTcc", rollbackMethod = "cancel")

@BusinessActionContextParameter
该注解用来修饰 Try 方法的入参,被修饰的入参可以在 Commit 方法和 Rollback 方法中通过 BusinessActionContext 获取,例如:

@Override
public boolean cancel(BusinessActionContext context) {
    //todo 这里写中间件、非关系型数据库的回滚操作
    System.out.println("please manually rollback this data:" + context.getActionContext("params"));
    // 回滚
    stockMapper.subStockRollback();
    return true;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

竹峰的风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值