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

插曲: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则是从设置的日志库中读取。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

剑指JUC原理-18.同步协作

👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring源码、JUC源码🔥如果感觉博主的文章还不错的话,请👍三连支持&…

王道数据结构课后代码题p150 15.设有一棵满二叉树(所有结点值均不同),已知其先序序列为 pre,设计一个算法求其后序序列post。(c语言代码实现)

对一般二叉树,仅根据先序或后序序列,不能确定另一个遍历序列。但对满二叉树,任意一个结点的左、右子树均含有相等的结点数,同时,先序序列的第一个结点作为后序序列的最后个结点。 本题代码如下 void pretopost(char …

神奇工具!这7个软件让设计轻松起飞

作为一个设计小白,你还在问前辈们有没有好的设计软件吗?还是没地方问,只能去百度搜索?如果是这样,那么接下来的文章正好可以解决你的问题。本文将介绍7种常用的平面设计工具,每种平面设计工具都有自己的特点…

由于找不到msvcp140_1.dll无法继续执行代码怎么解决

msvcp140_1.dll是Microsoft Visual C库文件之一,丢失后可能导致程序无法正常运行。以下是一些关于解决msvcp140_1.dll丢失问题的方法以及丢失原因的介绍。 一、msvcp140_1.dll是什么? 作用:msvcp140_1.dll是Microsoft Visual C库文件&#…

JVS低代码表单自定义按钮的使用说明和操作示例

在普通的表单设计中,虽然自带的【提交】、【重置】、【取消】按钮可以满足基本操作需求,但在面对更多复杂的业务场景时,这些按钮的显示控制就显得有些力不从心。为了更好地满足用户在表单操作过程中的个性化需求,JVS低代码推出了表…

切换数据库的临时表空间为temp1 / 切换数据库的undo表空间为 undotbs01

目录 ​编辑 一、切换临时表空间 1、登录数据库 2、查询默认临时表空间 3、创建临时表空间temp1(我们的目标表空间) 4、修改默认temp表空间 5、查询用户默认临时表空间 6、命令总结: 二、切换数据库的undo表空间 1、查询默认undo表…

STM32——端口复用与重映射概述与配置(HAL库)

文章目录 前言一、什么是端口复用?什么是重映射?有什么区别?二、端口复用配置 前言 本篇文章介绍了在单片机开发过程中使用的端口复用与重映射。做自我学习的简单总结,不做权威使用,参考资料为正点原子STM32F1系列精英…

大话IEC104 规约

2. iec104 协议的帧结构 iec104 基于TCP/IP 传输,是一个应用层协议, 其帧结构被称为 APDU,APDU 一般由 APCI 和 ASDU组成。 2.1 APDU (Application Protocol Data Unit) APDU 被称为应用协议数据单元,也就是一个iec104 的协议帧…

【修车案例】一波形一案例(12)

故障车型:丰田CHR 故障现象:发动机异常抖动,尤其是在怠速时,诊断仪显示气缸3失火,先后更换过点火线圈、喷油嘴等,仍然没有修复。 示波器诊断:用示波器采集发动机怠速时气缸2、气缸3的压力波形。…

【Docker】Docker 网络

引言 Docker是一个开源的应用容器引擎,它允许开发者将应用及其依赖打包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows机器上,也可以实现虚拟化。Docker的主要优势之一是其网络功能,而网络功能的核心就是网络驱动…

浅析网络协议-HTTP协议

1.HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。 HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图…

安卓手机搭建博客网站发布公网访问:Termux+Hexo结合内网穿透工具轻松实现

文章目录 前言 1.安装 Hexo2.安装cpolar3.远程访问4.固定公网地址 前言 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 下面介绍在Termux中安装个人hexo博客并结合…

史上最详细的测试用例写作规范

软件测试用例得出软件测试用例的内容,其次,按照软件测试写作方法,落实到文档中,两者是形式和内容的关系,好的测试用例不仅方便自己和别人查看,而且能帮助设计的时候考虑的更周。 一个好的测试用例必须包含…

windows下QZipReader和QZipWriter解压缩zip格式文件(只针对纯文件,递归目前暂不处理)

# 运行效果 ui设计文件 采用了网格布局,组件跟随窗口最大化最小化 # .pro项目文件 这段代码是一个项目文件(.pro文件)中的内容,用于配置一个Qt项目的构建和部署规则。它包含了一些指令和设置,用于指定项目中需要编译的源代码文件、头文件、UI表单文件以及项目所依赖的Qt…

docker-compose安装es以及ik分词同义词插件

目录 1 前言 2 集成利器Docker 2.1 Docker环境安装 2.1.1 环境检查 2.1.2 在线安装 2.1.3 离线安装 2.2 Docker-Compose的安装 2.2.1 概念简介 2.2.2 安装步骤 2.2.2.1 二进制文件安装 2.2.2.2 离线安装 2.2.2.3 yum安装 3 一键安装ES及Kibana 3.1 yml文件的编写…

模拟实现qsort()

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - :来于“云”的“羽球人”。…

计算机网络第一章(计算机网络开篇)

目录 一.什么是计算机网络1.0 何为计算机网络1.1 什么是Internet?1.2 互联网与互连网1.3 互联网基础结构发展的三个阶段 二.什么是网络协议2.1 协议的三要素2.2 internet协议标准 三. 互联网的组成3.1 边缘部分3.11 端系统之间的通信 3.2 核心部分3.21 数据交换技术 四. 计算机…

物业管理服务预约小程序的效果如何

物业所涵盖的场景比较多,如小区住宅、办公楼、医院、度假区等,而所涵盖的业务也非常广,而在实际管理中,无论对外还是对内也存在一定难题: 1、品牌展示难、内部管理难 物业需求度比较广,设置跨区域也可以&…

STM32-HAL库09-CAN通讯(loopback模式)

一、所用材料: STM32F103C6T6最小系统板 STM32CUBEMX(HAL库软件) MDK5 串口调试助手 二、所学内容: 初步学习如何使用STM32的CAN通讯功能,在本章节主要达到板内CAN通讯的效果,即32发送CAN信息再在CAN接收…

华为ssl vpn配置案例

t先在命令行输入命令 v-gateway sslvpn interface GigabitEthernet1/0/2 private 打开在命令行建立的sslvpn名称 直接开网络权限最大的模式:网络扩展 建立用户完成后点击上面的应用: 用命令行加策略: security-policy default action p…