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

文章目录

  • 前言
  • 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 {@Autowiredprivate AccountMapper accountMapper;@Autowiredprivate AccountFreezeMapper accountFreezeMapper;@Override@Transactionalpublic void deduct(String userId, int money) {// 1.获取事务xidString 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);}@Overridepublic boolean confirm(BusinessActionContext ctx) {// 1.获取事务xidString xid = ctx.getXid();// 2.根据xid删除冻结记录int count = accountFreezeMapper.deleteById(xid);return count == 1;}@Overridepublic boolean cancel(BusinessActionContext ctx) {// 1.查询冻结记录String xid = ctx.getXid();AccountFreeze freeze = accountFreezeMapper.selectById(xid);// 2.恢复可用余额accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());// 3.将冻结金额清零,状态改为CANCELfreeze.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 {@Autowiredprivate AccountService accountService;@Autowiredprivate 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=SHservice.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: nacosnacos:server-addr: 127.0.0.1:8848username:password:group: DEFAULT_GROUPdata-id: client.propertiestx-service-group: default_tx_group # 事务组名称

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

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

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

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

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

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

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

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

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

相关文章

​​使用ENVI之大气校正(下)

再根据遥感影像的拍摄时间将Flight ate与Flight Time GMT (H:M:SS)填写&#xff0c;如要查询按如下方法 这里按照表中的内容修改 根据影像范围的经纬度与拍摄时间更改Atmospheric Model&#xff0c;更改完成后点击Multispectral Settings...在跳出的界面中选择GUI再点击Default…

轨迹规划——估计规划轨迹曲率代码实现

已经知道轨迹&#xff08;x&#xff0c;y&#xff0c;theta&#xff09;一系列点集合&#xff0c;根据之前一篇文章&#xff1a; Estimating the Trajectory Curvature Using Three Trajectory Points Output From Motion Planning 代码如下&#xff1a; #include<iostre…

「iOS」——单例模式

iOS学习 前言单例模式的概念单例模式的优缺点单例模式的两种模式懒汉模式饿汉模式单例模式的写法 总结 前言 在一开始学习OC的时候&#xff0c;我们初步接触过单例模式。在学习定时器与视图移动的控件中&#xff0c;我们初步意识到单例模式的重要性。对于我们需要保持的控件&a…

【StringUtils工具类】isEmpty 和 isBlank 的区别

isEmpty 和 isBlank 的区别 前言isEmpty系列StringUtils.isEmpty&#xff08;&#xff09;StringUtils.isNotEmpty&#xff08;&#xff09;StringUtils.isAnyEmpty&#xff08;&#xff09;StringUtils.isNoneEmpty&#xff08;&#xff09;如何插入一段漂亮的代码片 isBank系…

FastDFS架构和原理

FastDFS 项目地址&#xff1a;https://github.com/happyfish100FastDFS 主要功能包括&#xff1a;文件存储&#xff0c;同步和访问&#xff0c;基于高可用的负载均衡。FastDFS 非常适合基于文件服务的站点。FastDFS 有跟踪服务器&#xff08;Tracker Server&#xff09;、存储服…

【数据结构】排序算法---冒泡排序

文章目录 1. 定义2. 算法步骤3. 动图演示4. 性质5. 算法分析6. 代码实现C语言PythonJavaCGo 结语 1. 定义 冒泡排序&#xff08;英语&#xff1a;Bubble sort&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的…

001、Git开发流程规范

前言 本篇不详细介绍 Git 的使用&#xff0c;仅介绍基于 Git 的开发分支流程规范。 简述 Git 管理中&#xff0c;最重要的一个点就在于分支的管理。在项目开发中&#xff0c;一般涉及到 Git 的相关分支有&#xff1a; master/main: 主分支&#xff0c;版本正式发布的代码都用…

硬件看门狗导致MCU启动时间慢

最近&#xff0c;在项目交付过程中&#xff0c;我们遇到了一个有趣的问题&#xff0c;与大家分享一下。 客户的需求是&#xff1a;在KL15电压上电后&#xff0c;MCU需要在200ms内发送出第一包CAN报文数据。然而&#xff0c;实际测试结果显示&#xff0c;软件需要360ms才能发送…

基于python+django+vue的美术馆预约系统

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

Facebook隐私设置指南:如何更好地保护个人信息

在数字化时代&#xff0c;隐私保护成为了每个互联网用户面临的重要课题。Facebook&#xff0c;作为全球最大的社交网络平台之一&#xff0c;拥有庞大的用户基础和丰富的个人数据。因此&#xff0c;了解和管理Facebook的隐私设置对保护个人信息至关重要。本文将为您提供一份详细…

git push出错Push cannot contain secrets

报错原因&#xff1a; 因为你的代码里面包含了github token明文信息&#xff0c;github担心你的token会泄漏&#xff0c;所以就不允许你推送这些内容。 解决办法&#xff1a; 需要先把代码里面的github token信息删除掉&#xff0c;并且删掉之前的历史提交&#xff0c;只要包…

【推荐100个unity插件之34】在unity中实现和Live2D虚拟人物的交互——Cubism SDK for Unity

最终效果 文章目录 最终效果前言例子中文官网Live2d模型获取下载Live2D Cubism SDK for Unity使用文档限制unity导入并使用Live2D模型1、将SDK载入到项目2、载入模型3、显示模型4、 播放动画 表情动作修改参数眼神跟随看向鼠标效果部位触摸效果摸头效果摸头闭眼效果做成桌宠参考…

Mistral AI 又又又开源了闭源企业级模型——Mistral-Small-Instruct-2409

就在不久前&#xff0c;Mistral 公司在开源了 Pixtral 12B 视觉多模态大模型之后&#xff0c;又开源了自家的企业级小型模型 Mistral-Small-Instruct-2409 &#xff08;22B&#xff09;&#xff0c;这是 Mistral AI 最新的企业级小型模型&#xff0c;是 Mistral Small v24.02 的…

无人机如何突破高海拔高寒飞行环境?

无人机在突破高海拔高寒飞行环境方面&#xff0c;需要解决一系列技术难题和挑战。以下是一些主要的技术手段和策略&#xff1a; 1. 无人机平台设计与优化 增强机体结构&#xff1a;采用轻质高强度的材料&#xff0c;如碳纤维、复合材料等&#xff0c;减轻机身重量&#xff0c…

HomeAssistant显示节假日

先看效果 步骤&#xff1a; 新建卡片时选择“Markdown 卡片”代码在文章最下方&#xff0c;当然你也可以自己修改 点击保存/完成 ### {% if now().hour > 6 and now().hour < 9 -%} 早上好&#xff0c; {%- elif now().hour > 9 and now().hour < 12 -%} 上午好…

ipython里如何用?快速查阅帮助

1、&#xff1f;用于查询函数帮助文档&#xff0c;??用于查询带源码的帮助文档 ?用于搜索内容&#xff0c;*作为通配符。

javascript-原型和原型链

原型 每个函数都有一个默认的原型对象 - prototype ,通过 prototype 我们可以扩展 js 的内置对象。一个函数和它创建的实例共享这个函数的原型属性和方法。实例对象的 constructor 会指向构造函数 原型链 每个实例对象都会有一个隐式原型属性 __proto__,通过 __proto__ 指…

网络安全-shire写任务计划、反弹shell、写私钥、反序列化

目录 一、环境 二、 介绍 三、开始做题 四、写公钥 一、环境 网上自己找 二、 介绍 我们经过前面文章很清楚知道&#xff0c;shiro是将数据存储在内存当中&#xff0c;内存落盘实现一个数据存储&#xff0c;而当其结合python&#xff0c;python将登录的session存储到shiro里…

【隐私计算篇】不经意传输协议(OT/OTE)的进一步补充

1. 背景介绍 关于不经意传输(OT)和不经意传输扩展(OT Extension), 我们在之前的文章《OT&OT扩展(不经意传输扩展)深入浅出》做了详细的说明。但对于OT/OTE的一些技术或者概念&#xff0c;还有一定的内容欠缺&#xff0c;因此本文根据冯登国院士关于安全多方计算协议…

了解快充协议芯片诱骗取电过程

快充协议芯片诱骗取电的过程主要涉及充电器与设备之间的通信和电压协商&#xff0c;以确保安全、快速和高效的充电。这个过程依赖于快充协议芯片&#xff0c;如XSP08Q快充诱骗芯片&#xff0c;它们内置通信模块&#xff0c;能够与供电端的充电器进行握手通信&#xff0c;从而申…