1、TCC模式介绍

Seata 开源了 TCC 模式,该模式由蚂蚁金服贡献。TCC 模式需要用户根据自己的业务场景实现Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段 执行 Try 方式,在二阶段提交执行Confirm方法,二阶段回滚执行 Cancel 方法。

TCC 三个方法描述:

  • Try:资源的检测和预留;
  • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
  • Cancel:预留资源释放。

业务模型分 2 阶段设计:

用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。

以“扣钱”场景为例,在接入 TCC 前,对 A 账户的扣钱,只需一条更新账户余额的 SQL 便能完成;但是在接入 TCC 之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段 Try 成功的话二阶段 Confirm 一定能成功。

Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。

二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。

用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。

2、TCC模式改造案例

2.1、RM端改造

针对RM端,实现起来需要完成try/commit/rollback的实现,所以步骤相对较多但是前三步骤和AT模式一样

(1) 修改数据库表结构,增加预留检查字段,用于提交和回滚

ALTER TABLE `seata_order`.`t_order` ADD COLUMN `status` INT ( 0 ) NULL COMMENT '订单状态-0不可⽤,事务未提交 , 1-可⽤,事务提交';ALTER TABLE `seata_points`.`t_points` ADD COLUMN `frozen_points` INT ( 0 ) NULL DEFAULT 0 COMMENT '冻结积分' AFTER `points`;ALTER TABLE `seata_storage`.`t_storage` ADD COLUMN `frozen_storage` INT ( 0 ) NULL DEFAULT 0 COMMENT '冻结库存' AFTER `goods_id`;

(2)lagou_order工程改造

接口

package com.lagou.order.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.lagou.order.entity.Order;
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;/*** 接口被Seata管理,根据事务的状态完成提交或回滚操作*/
@LocalTCC
public interface OrderService extends IService<Order> {@TwoPhaseBusinessAction(name = "addTCC", commitMethod = "addCommit", rollbackMethod = "addRollback")// 该注解中name属性定义的名称必须保持全局唯一, commitMethod默认名称为"commit",rollbackMethod默认名称为"rollback"void add(@BusinessActionContextParameter(paramName = "order") Order order); // 该注解是将此方法中的Order参数放到BusinessActionContext上下文对象中,供我们定义的方法使用,paramName默认为 ""public boolean addCommit(BusinessActionContext context); // 该方法的返回值类型是固定的public boolean addRollback(BusinessActionContext context); // 该方法的返回值类型是固定的
}

实体类增加一个字段

package com.lagou.order.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;/*** 订单实体类*/
@Data
@TableName("t_order")
public class Order implements Serializable {@TableIdprivate Long id;//订单id@TableFieldprivate Integer goodsId;// 商品ID@TableFieldprivate Integer num;//商品数量@TableFieldprivate Double money;//商品总金额@TableFieldprivate java.util.Date createTime;//订单创建时间@TableFieldprivate String username;//用户名称@TableFieldprivate Integer status; // 订单状态
}

实现类

package com.lagou.order.service.impl;import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lagou.order.entity.Order;
import com.lagou.order.mapper.OrderMapper;
import com.lagou.order.service.OrderService;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import java.util.Date;
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Overridepublic void add(Order order) {order.setCreateTime(new Date());//设置订单创建时间order.setStatus(0); // try 阶段-预检查this.save(order);//保存订单}@Overridepublic boolean addCommit(BusinessActionContext context) {Object jsonOrder = context.getActionContext("order");Order order = JSON.parseObject(jsonOrder.toString(), Order.class);order = this.getById(order.getId());if (order != null) {order.setStatus(1); // 提交操作,1代表订单可用this.saveOrUpdate(order);}log.info("----------->xid"+context.getXid()+" 提交成功!");return true; // 注意方法必须返回为true}@Overridepublic boolean addRollback(BusinessActionContext context) {Object jsonOrder = context.getActionContext("order");Order order = JSON.parseObject(jsonOrder.toString(), Order.class);order = this.getById(order.getId());if (order != null) {this.removeById(order.getId()); // 回滚操作-删除订单}log.info("----------->xid"+context.getXid()+" 回滚成功!");return true;}
}

(3)lagou_points工程改造

接口改造

package com.lagou.points.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.lagou.points.entity.Points;
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;@LocalTCC
public interface PointsService extends IService<Points> {@TwoPhaseBusinessAction(name = "increaseTCC", commitMethod = "increaseCommit", rollbackMethod = "increaseRollback")public void increase(@BusinessActionContextParameter(paramName = "username") String username,@BusinessActionContextParameter(paramName = "points") Integer points);public boolean increaseCommit(BusinessActionContext context);public boolean increaseRollback(BusinessActionContext context);
}

实体类增加一个字段

package com.lagou.points.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;/*** 积分实体类*/
@Data
@TableName("t_points")
public class Points {@TableId(value = "ID", type = IdType.AUTO)private Integer id;//积分ID@TableFieldprivate String username;//用户名@TableFieldprivate Integer points;//增加的积分@TableFieldprivate Integer frozenPoints; // 冻结积分
}

实现类改造

package com.lagou.points.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lagou.points.mapper.PointsMapper;
import com.lagou.points.entity.Points;
import com.lagou.points.service.PointsService;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** 会员积分服务*/
@Slf4j
@Service
public class PointsServiceImpl extends ServiceImpl<PointsMapper, Points> implements PointsService {@AutowiredPointsMapper pointsMapper;/*** 会员增加积分** @param username 用户名* @param points   增加的积分* @return 积分对象*/public void increase(String username, Integer points) {QueryWrapper<Points> wrapper = new QueryWrapper<Points>();wrapper.lambda().eq(Points::getUsername, username);Points userPoints = this.getOne(wrapper);if (userPoints == null) {userPoints = new Points();userPoints.setUsername(username);// userPoints.setPoints(points);userPoints.setFrozenPoints(points); // try-设置冻结积分} else {// userPoints.setPoints(userPoints.getPoints() + points);userPoints.setFrozenPoints(points); // try-设置冻结积分}this.saveOrUpdate(userPoints);}@Overridepublic boolean increaseCommit(BusinessActionContext context) {// 查询⽤户积分QueryWrapper<Points> wrapper = new QueryWrapper<Points>();wrapper.lambda().eq(Points::getUsername, context.getActionContext("username"));Points userPoints = this.getOne(wrapper);if (userPoints != null) {// 增加用户积分userPoints.setPoints(userPoints.getPoints() + userPoints.getFrozenPoints());// 冻结积分清零userPoints.setFrozenPoints(0);this.saveOrUpdate(userPoints);}log.info("--------->xid=" + context.getXid() + " 提交成功!");return true;}@Overridepublic boolean increaseRollback(BusinessActionContext context) {// 查询用户积分QueryWrapper<Points> wrapper = new QueryWrapper<Points>();wrapper.lambda().eq(Points::getUsername, context.getActionContext("username"));Points userPoints = this.getOne(wrapper);if (userPoints != null) {// 冻结积分清零userPoints.setFrozenPoints(0);this.saveOrUpdate(userPoints);}log.info("--------->xid=" + context.getXid() + " 回滚成功!");return true;}
}

(4)lagou_stroage工程改造

接口改造

package com.lagou.storage.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.lagou.storage.entity.Storage;
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;/*** 仓库服务*/
@LocalTCC
public interface StorageService extends IService<Storage> {@TwoPhaseBusinessAction(name = "decreaseTCC", commitMethod = "decreaseCommit", rollbackMethod = "decreaseRollback")public void decrease(@BusinessActionContextParameter(paramName = "goodsId") Integer goodsId,@BusinessActionContextParameter(paramName = "quantity") Integer quantity);public boolean decreaseCommit(BusinessActionContext context);public boolean decreaseRollback(BusinessActionContext context);
}

实体类新增一个字段

package com.lagou.storage.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("t_storage")
public class Storage {@TableId(value = "id", type = IdType.AUTO)private Integer id;// 库存ID@TableFieldprivate String goodsId;// 商品ID@TableFieldprivate Integer storage;// 库存量@TableFieldprivate Integer frozenStorage;// 冻结库存
}

实现类改造

package com.lagou.storage.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lagou.storage.entity.Storage;
import com.lagou.storage.mapper.StorageMapper;
import com.lagou.storage.service.StorageService;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;/*** 仓库服务*/
@Service
@Slf4j
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> implements StorageService {/*** 减少库存** @param goodsId  商品ID* @param quantity 减少数量* @return 库存对象*/public void decrease(Integer goodsId, Integer quantity) {QueryWrapper<Storage> wrapper = new QueryWrapper<Storage>();wrapper.lambda().eq(Storage::getGoodsId, goodsId);Storage goodsStorage = this.getOne(wrapper);if (goodsStorage.getStorage() >= quantity) {// goodsStorage.setStorage(goodsStorage.getStorage() - quantity);// 设置冻结库存goodsStorage.setFrozenStorage(quantity);} else {throw new RuntimeException(goodsId + "库存不足,目前剩余库存:" + goodsStorage.getStorage());}this.saveOrUpdate(goodsStorage);}@Overridepublic boolean decreaseCommit(BusinessActionContext context) {QueryWrapper<Storage> wrapper = new QueryWrapper<Storage>();wrapper.lambda().eq(Storage::getGoodsId,context.getActionContext("goodsId"));Storage goodsStorage = this.getOne(wrapper);if (goodsStorage != null) {// 扣减库存goodsStorage.setStorage(goodsStorage.getStorage() - goodsStorage.getFrozenStorage());// 冻结库存清零goodsStorage.setFrozenStorage(0);this.saveOrUpdate(goodsStorage);}log.info("--------->xid=" + context.getXid() + " 提交成功!");return true;}@Overridepublic boolean decreaseRollback(BusinessActionContext context) {QueryWrapper<Storage> wrapper = new QueryWrapper<Storage>();wrapper.lambda().eq(Storage::getGoodsId, context.getActionContext("goodsId"));Storage goodsStorage = this.getOne(wrapper);if (goodsStorage != null) {// 冻结库存清零goodsStorage.setFrozenStorage(0);this.saveOrUpdate(goodsStorage);}log.info("--------->xid=" + context.getXid() + " 回滚成功!");return true;}
}

2.2、TM端改造

针对我们工程lagou_bussiness是事务的发起者,所以是TM端,其它工程为RM端. 所以我们只需要在lagou_common_db完成即可,因为lagou_bussiness方法里面没有对数据库操作.所以只需要将之前AT模式的代理数据源去掉即可.注意:如果lagou_bussiness也对数据库操作了.也需要完成try/commit/rollback的实现 。

代码实现:

package com.lagou.bussiness.service.impl;import com.lagou.bussiness.feign.OrderServiceFeign;
import com.lagou.bussiness.feign.PointsServiceFeign;
import com.lagou.bussiness.feign.StorageServiceFeign;
import com.lagou.bussiness.service.BussinessService;
import com.lagou.bussiness.utils.IdWorker;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** 业务逻辑*/
@Service
public class BussinessServiceImpl implements BussinessService {@AutowiredOrderServiceFeign orderServiceFeign;@AutowiredPointsServiceFeign pointsServiceFeign;@AutowiredStorageServiceFeign storageServiceFeign;@AutowiredIdWorker idWorker;/*** 商品销售** @param goodsId  商品id* @param num      销售数量* @param username 用户名* @param money    金额*/// @Transactional@GlobalTransactional(name = "sale", timeoutMills = 100000, rollbackFor = Exception.class)public void sale(Integer goodsId, Integer num, Double money, String username) {//创建订单orderServiceFeign.addOrder(idWorker.nextId(), goodsId, num, money, username);//增加积分pointsServiceFeign.increase(username, (int) (money / 10));//扣减库存storageServiceFeign.decrease(goodsId, num);}
}

示例代码下载

Seata-TCC模式相关推荐

  1. 阿里巴巴开源分布式框架Seata TCC模式深入分析

    2019 年 3 月,蚂蚁金服加入分布式事务 Seata 的社区共建中,并贡献其 TCC 模式.本期是 SOFAChannel 第四期,主题:分布式事务 Seata TCC 模式深度解析,本文根据觉生 ...

  2. Seata TCC模式实战

    前言 最近状态有点不好,所以创作动力不足,发觉日常生活一定要做减法,对少量的事保持持续专注的投入,养成良好的习惯. 今天补充下,Seata TCC模式实战. 一.TCC设计原则 从 TCC 模型的框架 ...

  3. 分布式事务 - Seata - TCC模式

    目录 一.什么是TCC 二.AT & TCC区别 及 适用场景 三.代码集成示例 3.1 升级Seata 1.5.2 3.2 示例场景说明 3.3 TCC核心接口定义 3.4 TCC相关阶段规 ...

  4. Seata TCC模式-TCC模式

    项目源码: https://gitee.com/benwang6/seata-samples 一.TCC 基本原理 TCC 与 Seata AT 事务一样都是两阶段事务,它与 AT 事务的主要区别为: ...

  5. seata TCC模式

    .Seata 产品模块 .Seata 中有三⼤模块,分别是 TM.RM 和 TC.其中 TM 和 RM 是作为 Seata 的客户端与业务系统集 成在⼀起,TC 作为 Seata 的服务端独⽴部署. ...

  6. 微服务seata 1.4.2 分布式事务TCC模式示例

    seata TCC模式和AT模式的基础环境是一样的,只是在实现方式上有所区别,而且TCC模式还可以和AT模式混合使用. 关于AT模式示例,可以参考seata 1.4.2 分布式事务AT模式示例. TC ...

  7. Seata之TCC模式

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

  8. 多个mapper的事务回滚_揭秘蚂蚁金服分布式事务 Seata 的AT、Saga和TCC模式

    作者| 屹远(陈龙),蚂蚁金服分布式事务核心研发 . 导语 本文根据 8月11日 SOFA Meetup#3 广州站 <分布式事务 Seata 及其三种模式详解>主题分享整理,着重分享分布 ...

  9. 实战~阿里神器 Seata 实现 TCC模式 解决分布式事务,真香

    今天这篇文章介绍一下Seata如何实现TCC事务模式,文章目录如下: 什么是TCC模式? TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交.是目前最火的一种柔性事务 ...

  10. 实战!阿里神器 Seata 实现 TCC模式 解决分布式事务,真香!

    ‍ 今天这篇文章介绍一下Seata如何实现TCC事务模式,文章目录如下: 目录   什么是TCC模式? TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交.是目前最火 ...

最新文章

  1. Spring Cloud Alibaba 之 RPC 消息:Dubbo 与 Nacos 体系如何协同作业
  2. php xml 动态添加数据,php向xml中添加数据一例
  3. 防止我们账号被盗的5个方法
  4. [AX]AX2012开发新特性-全文索引
  5. elementUI清空弹框中的表单数据
  6. android 不通过数据线打印日志_人人都可写代码-Android零基础编程-开发调试、APK编译04...
  7. stats | 介绍三个与数学规划函数
  8. FFmpeg总结(十一)用ffmpeg进行转格式,Android下播放网络音频流
  9. ixgbe驱动不支持三方兼容光模块SFP+SFP+或者QSFP的解决方案
  10. 关于最新版的JCreator只能编译不能运行的问题
  11. eclipse 安装windows builder的问题及解决办法
  12. 机器学习基础(二)——训练集和测试集的划分
  13. C++程序设计:字符图形输出(空白三角形)
  14. A*算法在Unity中的实现
  15. excel自动汇总多个工作表数据
  16. minecraft崩溃java,je1.7.10,进入世界就崩溃,解决一下
  17. 高数函数的连续性与间断点
  18. python itchat实现微信自动回复
  19. 线下沙龙 | EOS入门及最新技术解读
  20. 信道编码之设计线性分组码

热门文章

  1. 力扣合并两个有序链表
  2. 2023华为od机试真题【拔河比赛】Python 实现
  3. 识别手写文字怎么弄?试试这几款软件吧
  4. ubuntu下最优秀的截屏软件scrot
  5. 性价比最高的云服务器购买方式
  6. java计算机毕业设计甜心驿站饮品信息管理源码+系统+数据库+lw文档+mybatis+运行部署
  7. java判断是否是Email格式
  8. 加一度分享:高薪信息流优化师必备技能
  9. 获取当前scn号scn1_深入理解SCN号
  10. 青岛新媒体运营教程:快手运营教程,如何从0到1,从1到100