概述

https://seata.io/zh-cn/docs/dev/mode/tcc-mode

https://seata.io/zh-cn/docs/user/mode/tcc

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

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

Seata的TCC模型

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

优缺点

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

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

TCC的优点是什么?

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

TCC的缺点是什么?

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

事务悬挂和空回滚

1)空回滚

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

如图:

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

2)业务悬挂

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

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

实现TCC模式

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

1)思路分析

这里我们定义一张表:

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项目中的com.mcode.account.service包中新建一个接口,声明TCC三个接口:

package com.mcode.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 { @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money")int money); boolean confirm(BusinessActionContext ctx); boolean cancel(BusinessActionContext ctx);
}

3)编写实现类

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

package com.mcode.account.service.impl;

import com.mcode.account.entity.AccountFreeze;
import com.mcode.account.mapper.AccountFreezeMapper;
import com.mcode.account.mapper.AccountMapper;
import com.mcode.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; @Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService { @Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper; @Override
@Transactional
public void deduct(String userId, int money) {
// 0.获取事务id
String xid = RootContext.getXID();
// 1.扣减可用余额
accountMapper.deduct(userId, money);
// 2.记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freezeMapper.insert(freeze);
} @Override
public boolean confirm(BusinessActionContext ctx) {
// 1.获取事务id
String xid = ctx.getXid();
// 2.根据id删除冻结记录
int count = freezeMapper.deleteById(xid);
return count == 1;
} @Override
public boolean cancel(BusinessActionContext ctx) {
// 0.查询冻结记录
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid); // 1.恢复可用余额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
// 2.将冻结金额清零,状态改为CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}

四种模式对比

我们从以下几个方面来对比四种实现:

  • 一致性:能否保证事务的一致性?强一致还是最终一致?
  • 隔离性:事务之间的隔离性如何?
  • 代码侵入:是否需要对业务代码改造?
  • 性能:有无性能损耗?
  • 场景:常见的业务场景

如图:

Spring Cloud 系列:Seata 中TCC模式具体实现的更多相关文章

  1. Spring Cloud系列文,Feign整合Ribbon和Hysrix

    在本博客之前的Spring Cloud系列里,我们讲述了Feign的基本用法,这里我们将讲述下Feign整合Ribbon实现负载均衡以及整合Hystrix实现断路保护效果的方式. 1 准备Eureka ...

  2. Spring Cloud系列(二) 介绍

    Spring Cloud系列(一) 介绍 Spring Cloud是基于Spring Boot实现的微服务架构开发工具.它为微服务架构中涉及的配置管理.服务治理.断路器.智能路由.微代理.控制总线.全 ...

  3. Spring Cloud 系列之 Consul 注册中心(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Consul 注册中心(一) 本篇文章讲解 Consul 集群环境的搭建. Consul 集群 上图是一个简单的 Co ...

  4. Spring Cloud 系列之 Sleuth 链路追踪(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Sleuth 链路追踪(一) 本篇文章讲解 Sleuth 基于 Zipkin 存储链路追踪数据至 MySQL,Elas ...

  5. spring cloud系列教程第一篇-介绍

    spring cloud系列教程第一篇-介绍 前言: 现在Java招聘中最常见的是会微服务开发,微服务已经在国内火了几年了,而且也成了趋势了.那么,微服务只是指spring boot吗?当然不是了,微 ...

  6. Spring Cloud 系列之 Consul 配置中心

    前面我们已经学习过 Spring Cloud Config 了: Spring Cloud 系列之 Config 配置中心(一) Spring Cloud 系列之 Config 配置中心(二) Spr ...

  7. Spring Cloud 系列之 Alibaba Nacos 注册中心(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Alibaba Nacos 注册中心(一) 本篇文章讲解 Nacos 注册中心集群环境搭建. Nacos 集群环境搭建 ...

  8. Spring Cloud系列教程第九篇-Eureka自我保护机制

    Spring Cloud系列教程第九篇-Eureka自我保护机制 本文主要内容: 1:自我保护介绍 2:导致原因分析 3:怎么禁止自我保护 本文是由凯哥(凯哥Java:kagejava)发布的< ...

  9. Spring Cloud Netflix项目进入维护模式

    任何项目都有其生命周期,Spring Could Netflix也不例外,官宣已进入维护模式,如果在新项目开始考虑技术选型时要考虑到这点风险,并考虑绕道的可能性. 原创: itmuch  IT牧场 这 ...

  10. Spring Cloud 系列之 Spring Cloud Stream

    Spring Cloud Stream 是消息中间件组件,它集成了 kafka 和 rabbitmq .本篇文章以 Rabbit MQ 为消息中间件系统为基础,介绍 Spring Cloud Stre ...

随机推荐

  1. 2023年最后一个工作日,当 hr总监找上我协商赔偿

    今天是2023年最后一个工作日,hr 总监找上我协商赔偿一事,忆往昔三年前,公司刚融资1个亿,意气风发,博主入职即为公司巅峰,高级开发岗,14薪,各种福利,加班另算加班费,业务主要服务于众多500强集 ...

  2. 【GIT-精讲】从零玩转Git-基础理论

    关于版本控制 一.什么是版本控制 版本控制(Version Control Systems)版本控制(Revision control)是一种软件工程技巧 在开发的过程中,确保由不同人所编辑的同一档案 ...

  3. 超详细API插件使用教程,教你开发AI垃圾分类机器人

    本文分享自华为云社区[案例教学]华为云API对话机器人的魅力-体验AI垃圾分类机器人,作者:华为云PaaS服务小智. 体验用Huawei Cloud API开发AI垃圾分类机器人,并学习AI自然语言的 ...

  4. 技术实践丨基于MindSpore的ResNet-50蘑菇“君”的识别应用体验

    本文分享自华为云社区<基于MindSpore的ResNet-50蘑菇"君"的识别应用体验>,原文作者:Dasming. 摘要:基于华为MindSpore框架的ResNe ...

  5. 数仓在线运维:如何进行在线增删CN?

    摘要:集群运行过程中,根据集群的综合负载和业务接入情况进行分析:增加CN可以适当降低CPU消耗,增大接入连接数,分散CN节点业务压力,根据实际情况来识别是否要增加CN,如果是提升集群容量和扩展比能力, ...

  6. 云图说丨带你了解GaussDB(for Redis)双活解决方案

    摘要:GaussDB(for Redis)推出了双活解决方案,基于GaussDB NoSQL统一架构,通过两个数据库实例之间的数据同步,达成数据的一致性. 本文分享自华为云社区<[云图说]一张图 ...

  7. 云小课 | 华为云KYON之私网NAT网关

    摘要:本文介绍KYON独创的私网NAT网关服务,支持云上重叠组网,支持云上重叠组网,助您的业务敏捷上云. 本文分享自华为云社区<云小课 | 华为云KYON之私网NAT网关>,原文作者:云小 ...

  8. 数仓出现“wait in ccn queue”的时候,怎么迅速定位处理?

    摘要:现网在使用动态负载管理的时候,经常出现很多wait in ccn的情况,大家处理起来就会认为是hung住或者怎么着了,很着急,但wait ccn其实就是一个等待资源的状态,在此总结一个ccn问题 ...

  9. 加快云原生技术转型, 智能调度登陆华为云DevOps: 增速,节源

    摘要:本文将探讨智能资源调度在华为云DevOps上的应用与实践. 本文分享自华为云社区<加快云原生技术转型, 智能调度登陆华为云DevOps: 增速,节源>,作者: DevAI. 1. 背 ...

  10. k8s源码Client-go中Reflector解析

    摘要:通过本文,可以了解Reflector通过ListWatcher从Kubernetes API中获取对象的流程,以及存储到store中,后续会对DeltaFIFO进行源码研读,通过结合inform ...