32-微服务技术栈(高级):分布式事务Seata的TCC模式

简介: 在分布式架构系统中,服务不止一个,一个完整的业务链路肯定也不止调用一个服务,此时每个服务都有自己的数据库增删改查,而每一个写操作对应一个本地事务。如果想要确保全部的业务状态一致,也就意味着需要所有的本地事务状态一致,这在我们之前的学习中肯定是不具备的,如何做到跨服务、跨数据源的事务一致性将是本章节的重点学习内容。

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;
  • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
  • Cancel:预留资源释放,可以理解为try的反向操作。

1.流程分析

举例,一个扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。

  • 阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30

初始余额:

余额充足,可以冻结:

此时,总金额 = 冻结金额 + 可用金额,数量依然是100不变。事务直接提交无需等待其它事务。

  • 阶段二(Confirm):假如要提交(Confirm),则冻结金额扣减30

确认可以提交,不过之前可用金额已经扣减过了,这里只要清除冻结金额就好了:

此时,总金额 = 冻结金额 + 可用金额 = 0 + 70  = 70元

  • 阶段二(Canncel):如果要回滚(Cancel),则冻结金额扣减30,可用余额增加30

需要回滚,那么就要释放冻结金额,恢复可用金额:

2.Seata的TCC模型

Seata中的TCC模型依然延续之前的事务架构,如图:

3.优缺点

TCC模式的每个阶段是做什么的?

  • Try:资源检查和预留
  • Confirm:业务执行和提交
  • Cancel:预留资源的释放

TCC的优点是什么?

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点是什么?

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理

4.事务悬挂和空回滚

1)空回滚

当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚

如图:

执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。

2)业务悬挂

对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是业务悬挂

执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂

5.实现TCC模式

解决空回滚和业务悬挂问题,必须要记录当前事务状态,是在try、还是cancel?

1)思路分析

这里我们定义一张表;在 seata_demo数据库中创建如下表:

CREATE TABLE `account_freeze_tbl`(

   `xid` varchar(128) NOT NULL,

   `user_id` varchar(255) DEFAULT NULL COMMENT'用户id',

   `freeze_money`int(11) unsigned DEFAULT'0'COMMENT'冻结金额',

   `state`int(1) DEFAULT NULL COMMENT'事务状态,0:try,1:confirm,2:cancel',

   PRIMARY KEY(`xid`)USING BTREE

)ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

其中:

  • xid:是全局事务id
  • freeze_money:用来记录用户冻结金额
  • state:用来记录事务状态

那此时,我们的业务该怎么做呢?

  • Try业务:
  • 记录冻结金额和事务状态到account_freeze表
  • 扣减account表可用金额
  • Confirm业务
  • 根据xid删除account_freeze表的冻结记录
  • Cancel业务
  • 修改account_freeze表,冻结金额为0,state为2
  • 修改account表,恢复可用金额
  • 如何判断是否空回滚?
  • cancel业务中,根据xid查询account_freeze,如果为null则说明try还没做,需要空回滚
  • 如何避免业务悬挂?
  • try业务中,根据xid查询account_freeze ,如果已经存在则证明Cancel已经执行,拒绝执行try业务

接下来,我们改造account-service,利用TCC实现余额扣减功能。

2)声明TCC接口

TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明,

我们在account-service项目中的cn.itcast.account.service包中新建一个接口,声明TCC三个接口:

package cn.itcast.account.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;


@LocalTCC

public interface AccountTCCService {


   /**

    * 根据用户id扣减余额,记录冻结金额

    * commitMethod 提交时对于的方法;rollbackMethod回滚时对于的方法

    * @param userId 用户id

    * @param money 扣减金额

    */

   @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")

   void deduct(@BusinessActionContextParameter("userId")String userId,

               @BusinessActionContextParameter("money")int money);


   /**

    * 执行TCC事务时,提交事务时执行的方法

    * @param ctx 上下文对象

    * @return 执行结果

    */

   Boolean confirm(BusinessActionContext ctx);

   /**

    * 执行TCC事务时,回滚事务时执行的方法

    * @param ctx 上下文对象

    * @return 执行结果

    */

   Boolean cancel(BusinessActionContext ctx);

}

3)编写实现类

account-service服务中的cn.itcast.account.service.impl包下新建一个类,实现TCC业务:

package cn.itcast.account.service.impl;


import cn.itcast.account.entity.AccountFreeze;

import cn.itcast.account.mapper.AccountFreezeMapper;

import cn.itcast.account.mapper.AccountMapper;

import cn.itcast.account.service.AccountTCCService;

import io.seata.core.context.RootContext;

import io.seata.rm.tcc.api.BusinessActionContext;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;


@Service

public class AccountTCCServiceImpl implements AccountTCCService {


   @Autowired

   private AccountMapper accountMapper;


   @Autowired

   private AccountFreezeMapper accountFreezeMapper;


   @Override

   @Transactional

   public void deduct(String userId, int money) {

       //获取事务id

       String xid = RootContext.getXID();

       //处理业务悬挂

       AccountFreeze freeze = accountFreezeMapper.selectById(xid);

       if (freeze != null) {

           //已经cancel过,拒绝业务

           return;

       }


       //1 扣减余额

       accountMapper.deduct(userId, money);


       //2 记录冻结金额

       AccountFreeze accountFreeze = new AccountFreeze();

       accountFreeze.setXid(xid);

       accountFreeze.setUserId(userId);

       accountFreeze.setFreezeMoney(money);

       accountFreeze.setState(AccountFreeze.State.TRY);

       accountFreezeMapper.insert(accountFreeze);

   }


   @Override

   public Boolean confirm(BusinessActionContext ctx) {

       //删除冻结金额

       String xid = ctx.getXid();

       int count = accountFreezeMapper.deleteById(xid);

       return count==1;

   }


   @Override

   public Boolean cancel(BusinessActionContext ctx) {

       //获取全局事务id

       String xid = ctx.getXid();

       //用户id

       String userId = ctx.getActionContext("userId").toString();

       AccountFreeze accountFreeze = accountFreezeMapper.selectById(xid);


       //处理空回滚

       if (accountFreeze == null) {

           accountFreeze = new AccountFreeze();

           accountFreeze.setXid(xid);

           accountFreeze.setUserId(userId);

           accountFreeze.setFreezeMoney(0);

           accountFreeze.setState(AccountFreeze.State.CANCEL);

           accountFreezeMapper.insert(accountFreeze);

           return true;

       }


       //幂等处理

       if (accountFreeze.getState() == AccountFreeze.State.CANCEL) {

           //说明已经回滚过了;不需要再处理

           return true;

       }


       //1 回退金额

       accountMapper.refund(userId, accountFreeze.getFreezeMoney());


       //2 更新冻结金额为0和状态

       accountFreeze.setFreezeMoney(0);

       accountFreeze.setState(AccountFreeze.State.CANCEL);

       int count = accountFreezeMapper.updateById(accountFreeze);


       return count==1;

   }

}

改造 src/main/java/cn/itcast/account/web/AccountController.java 如下:

相关文章
|
10月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1439 3
|
9月前
|
存储 安全 Java
管理 Spring 微服务中的分布式会话
在微服务架构中,管理分布式会话是确保用户体验一致性和系统可扩展性的关键挑战。本文探讨了在 Spring 框架下实现分布式会话管理的多种方法,包括集中式会话存储和客户端会话存储(如 Cookie),并分析了它们的优缺点。同时,文章还涵盖了与分布式会话相关的安全考虑,如数据加密、令牌验证、安全 Cookie 政策以及服务间身份验证。此外,文中强调了分布式会话在提升系统可扩展性、增强可用性、实现数据一致性及优化资源利用方面的显著优势。通过合理选择会话管理策略,结合 Spring 提供的强大工具,开发人员可以在保证系统鲁棒性的同时,提供无缝的用户体验。
199 0
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
442 5
seata是怎么进行分布式事务控制的
seata是怎么进行分布式事务控制的
|
Java 关系型数据库 数据库
微服务SpringCloud分布式事务之Seata
SpringCloud+SpringCloudAlibaba的Seata实现分布式事务,步骤超详细,附带视频教程
1194 1
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
903 6
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
471 1
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2
|
负载均衡 Java 应用服务中间件
微服务分布式系统架构之zookeeper与dubbor-1
微服务分布式系统架构之zookeeper与dubbor-1

热门文章

最新文章