# cloud-seata-tcc **Repository Path**: ly-springcloud/cloud-seata-tcc ## Basic Information - **Project Name**: cloud-seata-tcc - **Description**: SpringCloud Seata TCC模式 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-09-28 - **Last Updated**: 2025-09-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: SpringCloud ## README ##### 一. 框架构成     AT模式以及服务的搭建[请看上一篇教程](https://gitee.com/LYL-Y/cloud-seata), 本教程,采用SpringCloud Hoxton.SR8版 为分布式底层框架,持久层使用Mybatis-Plus,缓存为Redis,注册中心为阿里巴巴的Zookeeper(zk),各个服务间的通信使用的是openfeign,使用阿里巴巴Seata作为分布式事务,本教程采用分布式的默认TCC模式。 ###### 1.AT和TCC模式的优缺点 都是二阶段提交,AT模式下,用户不用过多的关心业务sql,而TCC模式下,代码侵入性较强,所有事务都要手动实现Try,Confirm,Cancel三个方法 TCC执行效率更高,AT模式下,在本地事务提交前,要尝试先拿到该记录的全局锁TCC模式下,不需要对数据加全局锁,允许多个事务同时操作数据,因此TCC是高性能分布式事务的解决方案,适用于对性能有很高要求的场景 ###### 2.TCC的三个阶段 Try:完成所有业务检查,预留必须的业务资源 Confirm:真正执行的业务逻辑,不做任何业务检查,只使用Try阶段预留的业务资源。因此只要Try操作成功,Confirm一定能成功 Cancel:释放Try阶段预留的业务资源,同样Cancel操作也需要满足幂等性 ##### 二. 简单介绍阿里巴巴分布式事务Seata     Seata 是阿里开源的分布式事务框架,属于二阶段提交模式,首先简单了解下数据库层面上的分布式解决方案 a.数据库层面的XA方案 ``` RM(Resource Manager): 用于直接执行本地事务的提交和回滚。在分布式集群中,一台MySQL服务器就是一个RM。 TM(Transaction Manager): TM是分布式事务的核心管理者。事务管理器与每个RM进行通信,协调并完成分布式事务的处理。 发起一个分布式事务的MySQL客户端就是一个TM ``` XA的两阶段提交分为Prepare阶段和Commit阶段,过程如下: ``` 准备(prepare)阶段: 即所有的RM锁住需要的资源,在本地执行这个事务(执行sql,写redo/undo log等), 但不提交,然后向Transaction Manager报告已准备就绪。 提交(commit)阶段: 当Transaction Manager确认所有参与者都ready后,向所有参与者发送commit命令。 ``` 如图所示: ![输入图片说明](https://images.gitee.com/uploads/images/2021/0916/155034_1692541e_1198099.png "微信截图_20210916155015.png") b.seata Seata是阿里开源的分布式事务解决方案中间件,对业务侵入小,核心概念包含三个角色: ``` TM:事务发起者。用来告诉TC全局事务的开始,提交,回滚。 RM:事务资源,每一个RM都会作为一个分支事务注册在TC。 TC:事务协调者,即独立运行的seata-server,用于接收事务注册,提交和回滚。 ``` ![输入图片说明](https://images.gitee.com/uploads/images/2021/0916/154954_04db50af_1198099.png "20190818220614755.png") 具体的执行流程如下: ``` 用户服务的 TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。 用户服务的 RM 向 TC 注册 分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入 XID 对应全局事务的管辖。 用户服务执行分支事务,向用户表插入一条记录。 逻辑执行到远程调用积分服务时(XID 在微服务调用链路的上下文中传播)。积分服务的RM 向 TC 注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入 XID 对应全局事务的管辖。 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务。 用户服务分支事务执行完毕。 TM 向 TC 发起针对 XID 的全局提交或回滚决议。 TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。 ``` ##### 三. 模拟实际应用 模拟一个普通的订单库存交易流程,用户创建订单后扣减库存,在service层加上@GlobalTransactional注解即可,如下是部分代码,完整代码可去 cloud-seata-order程序中查看,这里服务间的通讯使用的是feign ``` @Override @GlobalTransactional(rollbackFor = Exception.class) public void create(OrderCreateDto createDto) { log.info("---------->开始交易"); createDto.setId(IdWorker.get32UUID()); boolean result = orderTccAction.createOrder(null,createDto); // if (result){ // throw new RuntimeException(); // } //扣减库存 log.info("扣减库存开始"); storageFeignService.decreaseStorage(new DecreaseStorageDto(createDto.getProductId(),createDto.getCount())); log.info("扣减库存结束"); log.info("交易结束!"); } ``` ###### 1.订单模块: Try阶段,生成订单,但是将订单状态设为冻结状态,这里使用1表示订单的冻结状态,0表示正常状态 Confirm阶段,提交事务,将订单从冻结状态修改为正常状态 Cancel阶段,回滚事务,删除订单 TCC 接口,类上一定要加@@LocalTCC注解来开启TCC模式,方法中的参数BusinessActionContext 是一个上下文对象,用来在两个阶段之间传递数据。@BusinessActionContextParameter 注解的参数数据会被存入 BusinessActionContext ``` /** * 一阶段的try阶段 * commitMethod要对应二阶段提交的方法名 * rollbackMethod要对应二阶段回滚的方法名 * @return */ @TwoPhaseBusinessAction(name = "orderAction",commitMethod = "commit",rollbackMethod = "rollback") boolean createOrder(BusinessActionContext businessActionContext, @BusinessActionContextParameter(paramName = "order") OrderCreateDto orderCreateDto); /*** * 二阶段提交 * @param businessActionContext * @return */ boolean commit(BusinessActionContext businessActionContext); /** * 二阶段回滚 * @param businessActionContext * @return */ boolean rollback(BusinessActionContext businessActionContext); ``` TCC实现 ``` @Override @Transactional public boolean createOrder(BusinessActionContext businessActionContext, OrderCreateDto orderCreateDto) { //创建订单,但是把订单的状态改为冻结 log.info("创建订单开始!"); orderCreateDto.setStatus(1); int result = tblOrderMapper.insert(orderCreateDto); log.info("创建订单:tcc一阶段try成功"); return result > 0; } @Override @Transactional public boolean commit(BusinessActionContext businessActionContext) { //提交,一阶段成功,该阶段必须成功 String order = JSON.toJSONString(businessActionContext.getActionContext("order")); OrderCreateDto createDto = JSON.parseObject(order,OrderCreateDto.class); createDto.setStatus(0); int result = tblOrderMapper.update(createDto,new LambdaQueryWrapper().eq(TblOrder::getId,createDto.getId())); log.info("创建订单:tcc二阶段commit成功"); return result > 0; } @Override public boolean rollback(BusinessActionContext businessActionContext) { String order = JSON.toJSONString(businessActionContext.getActionContext("order")); OrderCreateDto createDto = JSON.parseObject(order,OrderCreateDto.class); int result = tblOrderMapper.delete(new LambdaQueryWrapper().eq(TblOrder::getId,createDto.getId())); log.info("创建订单:tcc二阶段回滚成功"); return result > 0; } ``` ###### 2.仓储模块: Try阶段: 从库存数量中取出预留扣减的数量,进行冻结 Confirm阶段: 提交事务,使用冻结的库存数量完成业务数据处理 Cancel阶段: 回滚事务,将冻结的库存解冻,恢复至之前的库存数量 这里使用了Guava中的HashBasedTable类,来解决幂等性问题,以防止重复回滚 ``` public class IdempotentUtils { private static Table,String,String> map = HashBasedTable.create(); public static void addMarker(Class clazz,String xid,String marker){ map.put(clazz,xid,marker); } public static String getMarker(Class clazz,String xid){ return map.get(clazz,xid); } public static void removeMarker(Class clazz,String xid){ map.remove(clazz,xid); } } ``` TCC 接口 ``` /** * * @param businessActionContext * @param prodId 产品主键id * @param count 数量 * @return */ @TwoPhaseBusinessAction(name = "storageAction",commitMethod = "commit",rollbackMethod = "rollback") boolean decreaseStorage(BusinessActionContext businessActionContext, @BusinessActionContextParameter(paramName = "prodId") String prodId, @BusinessActionContextParameter(paramName = "count") Integer count); boolean commit(BusinessActionContext businessActionContext); boolean rollback(BusinessActionContext businessActionContext); ``` TCC实现 ``` @Override @Transactional public boolean decreaseStorage(BusinessActionContext businessActionContext, String prodId, Integer count) { if (Objects.nonNull(IdempotentUtils.getMarker(getClass(),businessActionContext.getXid()))){ log.info("已执行过try阶段"); return true; } //首先冻结 TblStorage tblStorage = tblStorageMapper.selectById(prodId); tblStorage.setTotal(tblStorage.getTotal() - count); tblStorage.setFrozen(tblStorage.getFrozen() + count); int result = tblStorageMapper.updateById(tblStorage); IdempotentUtils.addMarker(getClass(),businessActionContext.getXid(),"marker"); return result > 0; } @Override @Transactional public boolean commit(BusinessActionContext businessActionContext) { if (Objects.isNull(IdempotentUtils.getMarker(getClass(),businessActionContext.getXid()))){ log.info("已执行过commit阶段"); return true; } //产品id String prodId = businessActionContext.getActionContext("prodId").toString(); //订单数量 Integer count = Integer.parseInt(businessActionContext.getActionContext("count").toString()); TblStorage tblStorage = tblStorageMapper.selectById(prodId); //设置已使用库存 tblStorage.setUsed(tblStorage.getUsed() + count); //设置冻结 tblStorage.setFrozen(tblStorage.getFrozen() - count); int result = tblStorageMapper.updateById(tblStorage); IdempotentUtils.removeMarker(getClass(),businessActionContext.getXid()); return result > 0; } @Override @Transactional public boolean rollback(BusinessActionContext businessActionContext) { if (Objects.isNull(IdempotentUtils.getMarker(getClass(),businessActionContext.getXid()))){ log.info("已执行过rollback阶段"); return true; } //产品id String prodId = businessActionContext.getActionContext("prodId").toString(); //订单数量 Integer count = Integer.parseInt(businessActionContext.getActionContext("count").toString()); TblStorage tblStorage = tblStorageMapper.selectById(prodId); tblStorage.setTotal(tblStorage.getTotal() + count); tblStorage.setFrozen(tblStorage.getFrozen() - count); int result = tblStorageMapper.updateById(tblStorage); IdempotentUtils.removeMarker(getClass(),businessActionContext.getXid()); return result > 0; } ``` ##### 四. 启动及注意事项 ###### 1.启动 分别启动每个服务,如果出现如下,则启动成功 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0917/170610_3cbe3786_1198099.png "微信截图_20210910142900.png") ###### 2.注意事项 分别模拟该流程成功和失败的情况,观察数据库是否回滚。