TCC分布式事务----以Hmily框架为例

news2024/11/27 0:39:10

插曲:RocketMQ的Half Message

先引入一个插曲,RocketMQ为什么要有Half Message
在这里插入图片描述
为什么不在本地事务提交之后,直接发一个commit消息不就行了,为什么还要先发一个可以撤回的、不能被消费的half message,再执行本地事务呢?这其实是一种状态转移:Producer把事务开始执行这个状态转移到了RocketMQ的Server,这样一来,即使Producer再执行完本地事务之后进行重启,Server由于已经根据halfMessage知道了这个事务执行的状态,所以会去主动轮询Producer。因此HalfMessage的使用需要配合一个可以提供事务状态检查的接口。

TCC业界实现

tcc-transaction
https://github.com/changmingxie/tcc-transaction
tx-lcn
https://github.com/codingapi/tx-lcn
hmily
https://github.com/dromara/hmily

Hmily

这里小马哥讲的有明显两个问题

  1. 小马哥说每个服务的confirm是在try之后立马执行的,这其实是有问题的。真正的confirm是在所有的try都成功之后,发起者的try整个结束之后,由TxManager异步调用的
  2. undolog在TCC模式下根本就没用。小马哥一直在说什么undolog,但其实TCC模式下的补偿是由业务来实现的,而不是undolog。

除此之外,我还有额外的一个困惑

  1. 如果某个confirm/cancel执行失败了会怎么办,会重复调用吗?但是为什么示例给的confirm并不是一个幂等操作?在这里插入图片描述

源码分析

下面对Hmily的源码进行分析
在这里插入图片描述
makePayment方法执行的updateOrderStatus、accountService.payment、inventoryService.decrease其实是三个try操作,其中updateOrderStatus是本地服务调用,而剩下两个是RPC。本地的更新订单状态的try,对应的confirm和cancel通过@HmilyTCC这个注解进行指定,对应RPC调用的服务,服务提供者的方法上也有指定相应的Confirm和Cancel

accountService
在这里插入图片描述
inventoryService
在这里插入图片描述
ok,那到这里其实应该明朗了一些:多个分布式事务的Try被挨个调用,这些事务的Confirm和Cancel操作则通过注解被指定,我们很容易知道,框架一定会通过@HmilyTCC这个注解进行AOP,这样一来,在try操作的执行前后,就有相当大的发挥空间。

Hmily会怎么发挥呢?不妨先设想一下TCC面临的问题

问题一:Try失败

如果某个Try失败了,比如说,我accountService调用失败了,那此时会怎么样?按照TCC的思想,此时应该对orderService调用Cancel操作,因为orderService在accountService的Try之前已经Try过了。那么问题来了,accountService作为一个远程服务,应该如何通知orderService进行Cancel呢?因此,在accountService的切面中,afterThrowing一定要做的一件事情就是,通知已经Try过的服务进行Cancel。怎么通知呢?通知给谁呢?执行Cancel的线程和执行Try的是一个线程吗?从Hmily的官网可以看到,Hmily的Cancel和Confirm是由TxManager异步调用的,也就是说,TxManager是一个独立于这三个服务之外的一个线程或是进程,专门管理整个TCC的全局事务。所以,afterThrowing会通知TxManager,TxManager会调用Cancel
在这里插入图片描述

所以,下面的思路是,顺着刚刚的思路,找到AOP,分析TxManager
在这里插入图片描述
AOP的主要逻辑集中在 HmilyGlobalInterceptor#invoke
在这里插入图片描述
invoke先加载了事务的上下文,顾名思义,上下文指的是这几个分布式事务之间共享的一些信息,其通过RpcParameterLoader#load获得。从RpcParameterLoader可以看出,上下文应该是通过RPC框架,比如Dubbo,进行传递的。加载完上下文之后,会继续往下执行

在这里插入图片描述
从这里可以看出,先通过getRegistry获得一个注册表,然后从注册表中选出一个Handler,调用handleTransaction。

getRegistry的逻辑如下
在这里插入图片描述
也就是,通过注解从REGSTRY中选择一个注册表,REGISTRY是一个静态变量,已经被初始化过
在这里插入图片描述
我们是TCC注解,所以选择TCC的实现,这里的设计思路很像Dubbo 框架的SPI Loader
在这里插入图片描述
注册表中被放入了很多Handler,这些Handler通过角色来获取,事务发起者和事务参与者的Handler是不一样的。在我们的例子中,orderService就是一个事务发起者START,而accountService和inventoryService就是事务参与者PARTICIPANT。让我们回到invokeWithinTransaction方法,通过方法签名上的分布式事务注解、当前角色,选定Handler之后,调用具体的handleTransaction实现。那么选择的逻辑,也就是select是什么呢?
在这里插入图片描述
如果当前上下文为空,也就是RPC的源头,那么就是发起者,则返回发起者的Handler。如果上下文不为空,则从上下文中找到角色,返回对应角色的Handler。我们当前的角色是Start,那么就找Start的Handler
在这里插入图片描述
下面去分析 StarterHmilyTccTransactionHandler
在这里插入图片描述
这里有两个非常重要的角色:executor和disruptor
point.proceed()就是执行原本的方法逻辑,即调用3个try。一旦抛出异常,则会调用executor.globalCancel(currentTransaction),而顺利执行完毕的话,则会调用executor.globalConfirm(currentTransaction)。那么disruptor.getProvider().onData() 我理解是将cancel和confirm进行了异步化处理。
在这里插入图片描述
所以disruptor只是一个异步化手段,暂时不做深入分析,这里重点关注的还是executor

首先是preTry
在这里插入图片描述
preTry构建了上下文对象,因为现在是START,所以还没有上下文 。上下文中设置了当前的角色START,动作TRYING,类型TCC等信息,随后将上下文放入了HmilyContextHolder中了,这个HmilyContextHolder是一个ThreadLocal,方便后面RPC调用时随时获取。

preTry之后就是调用切点方法的proceed了, 为了符合时序,我们的分析思路最好不要从executor.globalCancel(currentTransaction)或者executor.globalConfirm(currentTransaction)开始。这是因为在执行这两步操作之前的proceed,其实是调用了三个try操作的,本地的try,即更新订单,是本地服务,而剩下的两个try都是rpc,也都被标注了@HmilyTCC注解,因此分析他们的Handler也是很有必要的。所以这就需要我们分析ParticipantHmilyTccTransactionHandler

在这里插入图片描述
这个还是挺有趣的。如果是TRYING阶段,则会执行具体的proceed,而如果是CONFIRM或者是CANCELING阶段,则不会去执行proceed了,而是调用participantConfirm/participantCancel。我们目前只是进行了Try操作,是TRYING阶段,第62行和69行可以看到,如果当前服务的Try执行成功了,则万事大吉,记录下日志之后就返回。而如果Try抛出异常,则会删除当前参与者的日志记录,并且将异常往外抛。这个往外抛异常的操作,毫无疑问会引起本次RPC调用的失败,最终会进入到StarterHmilyTccTransactionHandler的catch中。而这个日志记录我觉得也很关键,因为它可以用来判定,当前参与者是否完成了Try操作,这决定了一旦出错,是否要对它执行Cancel。

所以,下面分析的重点,就来到了事务发起者的globalCancel和globalConfirm
在这里插入图片描述
globalCancel会设置当前全局事务的状态为CANCELING,然后遍历事务的参与者,挨个执行cancel操作。这里有个问题,我作为事务的发起者,如何知道有哪些参与者呢?hmilyParticipants来自currentTransaction,而currentTransaction 在外层来自ThreadLocal,也就是说,事务参与者执行完逻辑之后,会更新全局事务currentTransaction,然后通过RPC返回给START方。具体操作在ParticipantHmilyTccTransactionHandler调用的executor.preTryParticipant中
在这里插入图片描述
那么回到cancel,这个cancel是怎么执行到远程服务的cancel方法的呢?
在这里插入图片描述
HmilyParticipant的cancelHmilyInvocation应该是指定了cancel方法的信息
在这里插入图片描述
在这里插入图片描述
executeRPC应该就是执行具体调用cancel的RPC逻辑了。
在这里插入图片描述
这里我有个疑问,为什么cancel的RPC需要我从START方去调用,难道不是远程自己调吗?如果是从START来调,那远程岂不还要导出cancel方法?
但其实我看到accountService的cancel方法并没有被作为接口导出
在这里插入图片描述
前面提到过,HmilyParticipant会在RPC调用链时拦截调用并被构建出来。
在这里插入图片描述
第83行,调用之前,在第77行就被构建出来了。调用之后,在 89行,被注册了,这个时候,START才得以感知到Participant的存在。

而在ParticipantHmilyTccTransactionHandler在TRYING阶段执行 executor.preTryParticipant 的时候,会对HmilyParticipant进行构建,此时指定了自己的cancel和confirm。不过这个是怎么传递给调用方的呢?
在这里插入图片描述

难绷。。终于知道了,这个根根不会传递给调用方,而是自己解析出来cancel和confirm的方法之后放到本地缓存里了
在这里插入图片描述

放完之后,在cancel和confirm逻辑中可以之间拿到并调用本地
在这里插入图片描述
而STRAT发起globalcancel时,是RPC,此时不管是cancelInvocation还是confirmInvocation,都是指向的try方法的,这点可以从RPC Filter的DubboHmilyTransactionFilter#buildParticipant中看出
在这里插入图片描述
ok,那没问题,不管是globalCancel还是globalConfrim,执行的RPC都是调用远程的Try,至于具体的Cancel和Confirm操作交给远程决定。

终于可以回答问题了:Try失败之后,遍历参与者列表(这个列表中只有已经Try成功的参与者),然后调用Cancel,调用逻辑是向参与者的try发起RPC,会被AOP拦截,不会执行try而是执行注解上的cancel。

问题二:Cancel失败或者Confirm 失败怎么办

一个很蛋疼的事情:如果Cancel 或者 Confirm 执行失败, Hmily不会对其进行重试或者补偿。
在这里插入图片描述
在这里插入图片描述

可以看到,Hmily将confirm和cancel丢入了异步任务中,并且没有对异常进行任何处理。

会这么草率吗?不是记得有日志吗?是不是应该另外启一个线程,然后重复Confirm或者Cancel,幂等性交给业务方保证。

哦哦哦,看我发现了什么

在这里插入图片描述
ok,说明还是有恢复服务的,浅找了一下
HmilyTransactionSelfRecoveryScheduled # selfTccRecovery,里面有详细的TCC异常恢复逻辑

在这里插入图片描述

恢复思路大概是:

  1. 有最大重试次数,超过次数直接设置状态为DEATH
  2. 每次恢复,需要锁住该行,然后调用confirm或者cancel

这种恢复逻辑的存在,就需要我们保证Confirm和Cancel操作的幂等性。

https://dromara.org/zh/blog/hmily_introduction.html

这里面还提到了很多,比如针对RPC集群场景下,如何保证TRY,和Confirm路由到不同节点时,仍然可以从缓存中找到HmilyParticipant对象。关键就在于
这里的CacheLoader的逻辑是,如果不存在key,则调用load进行加载,而load则是从设置的日志库中读取。

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

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

相关文章

FindMy网络帮助您找到电动车

随着科技的发展,我们的生活变得越来越智能化。而现在,这项技术已经深入到了我们的出行方式中。如果你是一位电动车主,那么你可能会遇到一个常见的问题:忘记你的电动车停在了哪里。这种情况在日常生活中时有发生,而现在…

磁盘的分区、格式化、检验与挂载 ---- fdisk,mkfs,mount

磁盘的分区、格式化、检验与挂载 磁盘管理是非常重要的,当我们想要再系统里面新增一块磁盘使用时,应执行如下几步: 对磁盘进行划分,以建立可用的硬盘分区 (fdisk / gdisk)对硬盘分区进行格式化&#xff0…

【微信公众号开发】1.1 微信公众号开发课程内容介绍

一、微信公众号介绍 1、公众号类型及基本介绍 服务号、订阅号、小程序之间的关联及区别 2、编辑模式的使用 非开发者使用微信公众号的方式,通过微信公众号提供的平台来编辑 3、开发模式及预备知识介绍 如果我们不想使用默认的编辑模式,可以在具备一…

首周 Web3 开发者激励开启!数据与钱包新锐派送福利

如何在 Web3 时代汇聚更多开发者的力量,寻找全新的技术发展机遇和突破点?打造生态繁荣、高度协作的社区生态是不二路径。现在,一系列更为开放、活跃、包容的开发者社区活动正向大家发起邀请,你确定不来看看吗? 为了吸…

Power Automate-创建审批流

提前在SharePoint上创建好对应的表 在创建中选择自动化云端流 选择当创建项时触发 选择站点和列表,再点击添加新步骤 搜索审批,点击进入 操作里的选项区别: 1)创建审批:创建一个审批任务 2)等待审批&…

【Linux】 mdir命令使用

mdir 为mtools工具指令,模拟MS-DOS的dir指令,可显示MS-DOS文件系统中的目录内容。 语法 mdir [参数][目录] mdir命令 -Linux手册页 命令选项及作用 执行令 mdir--help 执行命令结果 参数 -a  显示隐藏文件。-f  不显示磁盘所剩余的可用空间。-w…

踹他GPT 之 弄个大乐透助手

11月7日凌晨的OpenAI第一次开发者大会,除了速度提升、服务使用费用的减少、开发者生态以外,最让我震撼的是,GPTS是ChatGPT的定制版本,是通过简单配置就可以完成一个使用ChatGPT、集成系统和数据服务的智能助手。比如OpenAI开发者体…

用Go实现网络流量解析和行为检测引擎

1.前言 最近有个在学校读书的迷弟问我:大德德, 有没有这么一款软件, 能够批量读取多个抓包文件,并把我想要的数据呈现出来, 比如:源IP、目的IP、源mac地址、目的mac地址等等。我说:“这样的软件你要认真找真能找出不少开源软件, 但毕竟没有你自己的灵魂在里面,要不…

【Python】queue模块Queue对象

Python中的queue模块是一个同步队列类,实现了多生产者、多消费者队列,适用于在多线程之间安全地传递消息或其他数据。Queue提供了所有必需的锁定语义。 queue模块有三种类型的队列(只是队列中元素的提取顺序不同):先进…

小型洗衣机好用吗?最好用的迷你洗衣机

很多人会觉得小型洗衣机是智商税,没有必要专门买一个小型洗衣机来洗内衣,洗个内衣只需要两分钟的事情,需要花个几百块钱去入手一个洗衣机吗?然而清洗贴身衣物的并不是一件简单的事情,如果只是简单的搓洗,内…

Unity Mirror学习(一) SyncVars特性使用

官网中所说的网络对象,指的是挂了 NetworkIdentity组件的对象 官网中所说的玩家对象,指的是NetworkManager脚本上的PlayerPrefab预制体 这个概念对阅读官网文档很重要,我刚开始并不理解,走了歪路 SyncVars(同步变量&a…

wav格式如何转mp3?

wav格式如何转mp3?WAV格式是一种高品质的音频文件格式,其采用无损压缩技术存储音频数据。通常,WAV文件使用PCM编码方式将声音信号转换为数字信号,并按照一定规则存储到文件中。这种编码方式可以确保音频数据的完整性和准确性&…

电脑硬盘数据恢复哪个好?值得考虑的 8 个硬盘恢复软件解决方案

借助硬盘恢复软件,任何人都可以在家中恢复丢失的文件,而无需任何特殊技能。事实上,最困难的一步是选择最佳解决方案,因为可用选项的数量可能有点多。幸运的是,这篇文章可以为您提供帮助。 8 款顶级硬盘数据恢复软件解决…

MemcachedRedis构建缓存服务器 (数据持久化,主从同步,哨兵模式)

Memcached/redis是高性能的分布式内存缓存服务器,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web等应用的速度、 提高可扩展性。降低数据库读的压力 Nsql的优点:高可扩展性,分布式计算,低成本,…

EMERSON艾默生变频器维修M600/M701/M702

艾默生变频器维修常见系列: EV1000 系列:体积小,操作简便,适用于塑料机械、纺织机械、烟草机械、陶瓷机械、制药机械食品机械、印刷机械、包装机械、空调等专用设备配套。 EV2000 系列:功率范围广,功能更…

【Python】数据分析案例:世界杯数据可视化 | 文末送书

文章目录 前期数据准备导入数据 分析:世界杯中各队赢得的比赛数分析:先打或后打的比赛获胜次数分析:世界杯中的抛硬币决策分析:2022年T20世界杯的最高得分者分析:世界杯比赛最佳球员奖分析:最适合先击球或追…

Android—幸运抽奖火箭发射倒计时(第六次作业)

Android—幸运抽奖&&点火发射(第六次作业) 创建项目 准备工作 修改按钮的颜色,如果不修改这行代码,那么后期给按钮添加background属性的时候,按钮并不会发生变化。 设置按钮的样式文件btn_press_blue.xml&am…

innovus/ICC2:实际绕线层次有低于routing rule min layer的情况如何解决?

ICC2 这是因为routing rule的min layer是soft rule,如果希望min layer严格按照设置的来,还需要手动添加如下命令: set_routing_rule -min_layer_mode hard -rule xx [get_nets xx] innovus 设置route type是加入min_stack_layer选项。 create_route_…

react 修改less文件后保存,内存溢出,项目崩溃问题解决

一、完整报错 一个很老的react项目,因为没有package-lock.json版本锁,导致拉下来的时候,安装的依赖版本冲突,好不容易启动起来,修改less文件后只要一保存,项目就会崩溃,需要重启,报…

nginx配置和热部署实践

目录 一、nginx配置文件 1.配置文件 2.nginx配置文件语法 3.include 二、nginx.conf参数 1.user参数 2.nginx.conf重要的指令块 3.nginx命令行 三、nginx热部署功能实践 1.热部署的特点 2.大致流程 3.环境准备 4.备份旧nginx二进制文件 5.下载编译安装新的nginx …