分布式事务学习笔记(五)微服务实现Seata TCC模式、TC服务器高可用异地容灾

news2024/9/23 1:20:12

文章目录

  • 前言
  • 6 Seata TCC 模式
    • 6.1 实现原理
    • 6.2 优缺点
    • 6.3 空回滚和业务悬挂
      • 6.3.1 空回滚
      • 6.3.2 业务悬挂
    • 6.4 微服务实现TCC模式
      • 6.4.1 思路分析
      • 6.4.2 声明TCC接口
      • 6.4.3 编写实现类
      • 6.4.4 Controller类调用TCC接口
      • 6.4.5 修改配置文件application.yml
      • 6.4.6 重启微服务并测试
  • 7 TC服务高可用
    • 7.1 高可用架构模型
    • 7.2 实现TC服务高可用
      • 7.2.1 模拟异地容灾
      • 7.2.2 将事务组映射配置到Nacos
      • 7.2.3 微服务读取Nacos配置

前言

分布式事务学习笔记(一)分布式事务问题、CAP定理、BASE理论、Seata
分布式事务学习笔记(二)Seata架构、TC服务器部署、微服务集成Seata
分布式事务学习笔记(三)微服务实现Seata XA模式
分布式事务学习笔记(四)微服务实现Stata AT模式、Stata Saga模式介绍

6 Seata TCC 模式

6.1 实现原理

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,其架构如下图:

TCC 模式包含两个阶段,分别是:

  • 阶段一(Try):资源检测与预留阶段
  • 阶段二(Confirm):预留资源确认阶段
  • 阶段二(Cancel):预留资源释放阶段

以一个扣减用户余额的业务为例。假设账户A原本的余额为100,现在需要扣减30。

  • 阶段一(Try):检查余额是否充足,如果充足则冻结金额增加30,可用余额扣减30,总数还是100。完成后事务直接提交,无需等待。

  • 阶段二(Confirm):如果是确认提交操作,则冻结金额扣减30,可用余额不变,此时就只剩下可用余额70。

  • 阶段二(Cancel):如果是回滚操作,则冻结金额扣减30,可用余额增加30,恢复到初始状态。

6.2 优缺点

TCC的优点:

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

TCC的缺点:

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,实现复杂
  • 有软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,实现麻烦

6.3 空回滚和业务悬挂

6.3.1 空回滚

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

因此,在执行Cancel操作时,应当判断Try是否已经执行,如果尚未执行,则应该空回滚。

6.3.2 业务悬挂

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

因此,在执行Try操作时,应当判断Cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的Try操作,避免悬挂。

6.4 微服务实现TCC模式

6.4.1 思路分析

要解决空回滚和业务悬挂问题,就必须要记录当前事务状态是在Try还是Cancel阶段。为此,可以在数据库定义一张表来记录:

CREATE TABLE `t_account_freeze` (
 `xid` varchar(128) NOT NULL COMMENT '全局事务id',
 `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;

业务逻辑分析如下:

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

6.4.2 声明TCC接口

jd-account-service微服务的com.hsgx.account.service包下新建一个AccountTCCService接口:

@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);

}

6.4.3 编写实现类

jd-account-service微服务的com.hsgx.account.service.impl包下新建一个AccountTCCService接口的实现类:

@Slf4j
@Service
public class AccountTCCServiceImpl implements AccountTCCService {

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper accountFreezeMapper;

    @Override
    @Transactional
    public void deduct(String userId, int money) {
        // 1.获取事务xid
        String xid = RootContext.getXID();
        // 2.扣减可用余额
        accountMapper.deduct(userId, money);
        // 3.记录冻结金额,事务状态
        AccountFreeze freeze = new AccountFreeze();
        freeze.setUserId(userId);
        freeze.setFreezeMoney(money);
        freeze.setState(0);
        freeze.setXid(xid);
        accountFreezeMapper.insert(freeze);
    }

    @Override
    public boolean confirm(BusinessActionContext ctx) {
        // 1.获取事务xid
        String xid = ctx.getXid();
        // 2.根据xid删除冻结记录
        int count = accountFreezeMapper.deleteById(xid);
        return count == 1;
    }

    @Override
    public boolean cancel(BusinessActionContext ctx) {
        // 1.查询冻结记录
        String xid = ctx.getXid();
        AccountFreeze freeze = accountFreezeMapper.selectById(xid);
        // 2.恢复可用余额
        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
        // 3.将冻结金额清零,状态改为CANCEL
        freeze.setFreezeMoney(0);
        freeze.setState(2);
        int count = accountFreezeMapper.updateById(freeze);
        return count == 1;
    }

}

6.4.4 Controller类调用TCC接口

修改AccountController类的deduct方法,改为调用刚刚新建的TCC接口:

@RestController
@RequestMapping("account")
public class AccountController {

    @Autowired
    private AccountService accountService;
    @Autowired
    private AccountTCCService accountTCCService;

    @PutMapping("/{userId}/{money}")
    public ResponseEntity<Void> deduct(@PathVariable("userId") String userId, @PathVariable("money") Integer money){
        // accountService.deduct(userId, money);
        accountTCCService.deduct(userId, money);
        return ResponseEntity.noContent().build();
    }

}

6.4.5 修改配置文件application.yml

修改微服务下的配置文件application.yml,注释掉分布式事务的模式:

seata:
  # data-source-proxy-mode: AT

6.4.6 重启微服务并测试

假设现在商品库存为10,用户余额为1000。用户使用300金额购买了6件商品:

库存和资金充足,下单成功。此时库存剩余4,余额为700。

此时用户使用300金额再次购买了6件商品,由于库存不足,会下单失败:

查看jd-account-service微服务的日志,可以看到冻结金额被写入 t_account_freeze 表(Try阶段):

随后,事务回滚,先查询 t_account_freeze 表,在恢复余额:

由此可见,TCC模式的分布式事务生效了。

7 TC服务高可用

TC服务作为分布式事务的核心,一定要保证其高可用,因此需要搭建TC服务集群。

7.1 高可用架构模型

搭建TC服务集群,只需要启动多个TC服务,注册到Nacos即可,相对简单。

同时还支持异地容灾,例如一个TC服务集群在上海,另一个TC服务集群在杭州,当其中一个集群故障时自动切换到另外一个集群。

如上图所示,微服务基于事务组(tx-service-group属性)与TC服务集群的映射关系,来查找当前应该使用哪个集群。

7.2 实现TC服务高可用

7.2.1 模拟异地容灾

计划启动两台TC服务器:

节点名称IP地址端口号集群名称
Seata1127.0.0.19091SH
Seata2127.0.0.19092HZ

修改Seata服务的配置文件,启动9091服务:

将Seata服务目录服务一份,修改配置文件,启动9092服务:

此时可以在Nacos控制台看到这两个节点的服务:

进入“详情”可以看到它们分属两个集群:

7.2.2 将事务组映射配置到Nacos

新建一个配置,将tx-service-group与cluster的映射关系配置到Nacos配置中心:

配置的内容如下:

# 事务组映射关系
service.vgroupMapping.default_tx_group=SH

service.enableDegrade=false
service.disableGlobalTransaction=false
# 与TC服务的通信配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
# RM配置
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
# TM配置
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
# undo日志配置
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
client.log.exceptionRate=100

7.2.3 微服务读取Nacos配置

修改每一个微服务的application.yml文件,让微服务读取Nacos中的client.properties文件:

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username:
      password:
      group: DEFAULT_GROUP
      data-id: client.properties
  tx-service-group: default_tx_group # 事务组名称

从启动日志可以看到此时连接的TC服务端口是9091,集群是SH:

如果此时上海节点故障了,只需要在Nacos配置中心修改配置:

修改后,微服务自动切换到9092端口:

可见,TC服务集群的高可用已生效。

本节完,更多内容请查阅分类专栏:微服务学习笔记

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析
  • MyBatis3源码深度解析
  • Redis从入门到精通
  • MyBatisPlus详解
  • SpringCloud学习笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2144249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】常用指令(中)(附带基础指令的详细讲解、Linux的一些附加知识)

文章目录 前言1. Linux基础常用指令1.1 通配符 "*"1.2 man指令&#xff08;重要&#xff09;1.2.1 man指令的语法 1.3 何为"指令"&#xff1f;(附带知识)1.4 echo指令1.5 cat指令1.6 Linux下一切皆文件&#xff01;1.6.1 ">" 输出重定向1.6.2…

FP6296XR-G1 10A电流模式非同步PWM升压转换器芯片IC

一般说明 F1 6296是目前最先进的直流一直流转换器。是一个带有内置15mΩ功率MOSFET使此稳压器具有高功率效率。误差放大器的非逆变输入端连接到1.2V的精密基准电压。电流模式控制和外部补偿网络使系统稳定容易灵活。FP6296采用SOP-8L(EP)封装&#xff0c;可用于应用领域…

基于 K8S kubernetes 搭建 安装 EFK日志收集平台

目录 1、在k8s中安装EFK组件 1.1 安装elasticsearch组件 1.2 安装kibana组件 1.3 安装fluentd组件 文档中的YAML文件配置直接复制粘贴可能存在格式错误&#xff0c;故实验中所需要的YAML文件以及本地包均打包至网盘 链接&#xff1a;https://pan.baidu.com/s/15Ryaoa0_…

各大平台统遭入侵??区块链市场遭攻击损失近3亿!

今年&#xff0c;全球发生多起骇人听闻的勒索入侵软件攻击事件&#xff0c;黑客组织利用各种手段和技术&#xff0c;不断试图突破网络安全防线&#xff0c;窃取敏感信息、破坏系统运行&#xff0c;甚至进行勒索和敲诈&#xff0c;使得网络安全问题日益凸显其重要性和紧迫性。 S…

【北京迅为】《STM32MP157开发板使用手册》- 第三十五章 A7 和 M4 联合调试

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

【MySQL】表的操作【有关表结构的操作】【创建、查看、删除、修改表结构】

目录 表的操作1.创建表2.查看表结构3修改表3.1修改表名3.2添加字段/列3.3修改字段/列3.4删除字段/列3.5对单一字段/列 重命名 4.删除表 表的操作 1.创建表 创建表的语句语法&#xff1a; CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) ch…

基于SSM的在线家用电器销售系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSSMVueMySQL的在线家…

【学习笔记】SSL/TLS安全机制之HSTS

1、What&#xff1a;HSTS全称HTTP Strict Transport Security&#xff0c;HTTP严格传输安全。 2、Why&#xff1a;因为HTTP 在重定向到 HTTPS 之前存在漏洞 用户在浏览时很少明确输入 https:// 有时&#xff0c;用户正好通过http来访问网站&#xff0c;网站服务器知道这应该是…

43.哀家要长脑子了!

1.53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int maxSubArray(vector<int>& nums) {int pre 0, maxRes nums[0];for(int x : nums){pre max(pre x, x);maxRes max(maxRes, pre); }return maxRes;} }; 其实弄懂也就挺简…

初写MySQL四张表:(3/4)

我们已经完成了四张表的创建&#xff0c;学会了创建表和查看表字段信息的语句。 初写MySQL四张表:(1/4)-CSDN博客 初写MySQL四张表:(2/4)-CSDN博客 接下来&#xff0c;我们来学点对数据的操作&#xff1a;增 删 查&#xff08;一部分&#xff09;改 先来看这四张表以及相关…

python-简单的数据结构

题目描述 小理有一天在网上冲浪的时候发现了一道很有意思的数据结构题。 该数据结构形如长条形。 一开始该容器为空&#xff0c;有以下七种操作。 1 a从前面插入元素 a ; 2 从前面删除一个元素; 3 a从后面插入一个元素; 4 从后面删除一个元素; 5 将整个容器头尾翻转; 6 输出个…

存储数据的树形结构

目录 1、二叉查找树 2、平衡二叉树AVL Tree 3 、平衡多叉树B-Tree 4、BTree树 5 、红黑树 红黑树的应用 6.平衡树的旋转 mysql 索引数据结构&#xff1a; Btree 索引是B树在数据库中的一种实现&#xff0c;最为常见的。B树 中的B代表平衡&#xff0c;而不是二叉 1、二…

火山引擎数智平台:高性能ChatBI的技术解读和落地实践

导读&#xff1a;大模型能力的发展和成熟&#xff0c;催生出新一代智能化 BI—— ChatBI&#xff0c;即通过自然语言处理&#xff08;NLP&#xff09;与大型语言模型&#xff08;LLMs&#xff09;的结合&#xff0c;极大简化数据分析过程&#xff0c;提高效率并降低分析门槛。火…

剪画:视频怎么去水印?分享几个简单实用的视频去水印方法!

亲爱的小伙伴们&#xff0c;在视频创作的道路上&#xff0c;水印问题是不是常常让你感到困扰呢&#xff1f; 别担心&#xff0c;今天就来为大家详细介绍七种超实用的视频去水印方法&#xff0c;让你的视频制作更加顺畅。 一、剪画 - 短视频去水印 剪画是一款非常强大的视频处理…

双向NAT=源NAT+NAT Server,有这么6?

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 你们好&#xff0c;我的网工朋友。 随着移动设备的普及和云计算技术的发展&#xff0c;网络流量的规模和复杂度不断增加。网络地址转换&#xff…

像JSON一样使用ProtoBuf,空间还能缩小60%,性能提升100%

首发公众号:【赵侠客】 引言 在前面《释放你九成的带宽和内存&#xff1a;GZIP在解决Redis大Key方面的应用》一文中我使用GZIP算法可以将JSON格式数据的大小缩小88%从而节省了大量的存储和带宽资源&#xff0c;本文介绍另一种JAVA对象序列化神器——ProtoBuf&#xff08;Proto…

打破服务提供商的数据中心自动化障碍

在通信服务提供商&#xff08;CSP&#xff09;不断变革的背景下&#xff0c;数据中心发挥着越来越重要的作用。这些数据中心不仅是部署基于云的5G基础设施的重要组成部分&#xff0c;还在促进边缘计算和下一代企业解决方案的过程中发挥着关键作用。然而&#xff0c;随着数据中心…

YOLOv10改进系列,YOLOv10损失函数更换为Powerful-IoU(2024年最新IOU),助力高效涨点

改进前训练结果: 改进后的结果: 摘要 边界框回归(BBR)是目标检测中的核心任务之一,BBR损失函数显著影响其性能。然而,观察到现有基于IoU的损失函数存在不合理的惩罚因子,导致回归过程中锚框扩展,并显著减缓收敛速度。为了解决这个问题,深入分析了锚框扩展的原因。针…

PyCharm安装和使用教程(Windows系统)

一、pycharm基本使用 说明&#xff1a; PyCharm 是一款功能强大的 Python 编辑器&#xff0c; 本文简单的介绍下PyCharm 在 Windows下是如何安装的。 PyCharm 的下载地址&#xff1a;http://www.jetbrains.com/pycharm/download/#sectionwindows 如果进入网页时间过长或进不…