Apache Seata tcc 模块源码分析

本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。
本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。

一 .导读

spring 模块分析中讲到,Seata 的 spring 模块会对涉及到分布式业务的 bean 进行处理。项目启动时,当 GlobalTransactionalScanner 扫描到 TCC 服务的 reference 时(即tcc事务参与方),会对其进行动态代理,即给 bean 织入 TCC 模式下的 MethodInterceptor 的实现类。tcc 事务发起方依然使用 @GlobalTransactional 注解开启,织入的是通用的 MethodInterceptor 的实现类。

TCC 模式下的 MethodInterceptor 实现类即 TccActionInterceptor(spring模块) ,这个类中调用了 ActionInterceptorHandler(tcc模块) 进行 TCC 模式下事务流程的处理。

TCC 动态代理的主要功能是:生成TCC运行时上下文、透传业务参数、注册分支事务记录。

二 .TCC模式介绍

在2PC(两阶段提交)协议中,事务管理器分两阶段协调资源管理,资源管理器对外提供三个操作,分别是一阶段的准备操作,和二阶段的提交操作和回滚操作。

public interface TccAction {@TwoPhaseBusinessAction(name = "tccActionForTest" , commitMethod = "commit", rollbackMethod = "rollback")public boolean prepare(BusinessActionContext actionContext,@BusinessActionContextParameter(paramName = "a") int a,@BusinessActionContextParameter(paramName = "b", index = 0) List b,@BusinessActionContextParameter(isParamInProperty = true) TccParam tccParam);public boolean commit(BusinessActionContext actionContext);public boolean rollback(BusinessActionContext actionContext);
}

这是 TCC 参与者实例,参与者需要实现三个方法,第一个参数必须是 BusinessActionContext ,方法返回类型固定,对外发布成微服务,供事务管理器调用。

prepare:资源的检查和预留。例:扣减账户的余额,并增加相同的冻结余额。

commit:使用预留的资源,完成真正的业务操作。例:减少冻结余额,扣减资金业务完成。

cancel:释放预留资源。例:冻结余额加回账户的余额。

其中 BusinessActionContext 封装了本次事务的上下文环境:xid、branchId、actionName 和被 @BusinessActionContextParam 注解的参数等。

参与方业务有几个需要注意的地方:
1.控制业务幂等性,需要支持同一笔事务的重复提交和重复回滚。
2.防悬挂,即二阶段的回滚,比一阶段的 try 先执行。
3.放宽一致性协议,最终一致,所以是读已修改

三 . remoting 包解析

在这里插入图片描述

包中所有的类都是为包中的 DefaultRemotingParser 服务,Dubbo、LocalTCC、SofaRpc 分别负责解析各自RPC协议下的类。

DefaultRemotingParser 的主要方法:
1.判断 bean 是否是 remoting bean,代码:

    @Overridepublic boolean isRemoting(Object bean, String beanName) throws FrameworkException {//判断是否是服务调用方或者是否是服务提供方return isReference(bean, beanName) || isService(bean, beanName);}

2.远程 bean 解析,把 rpc类 解析成 RemotingDesc,,代码:

@Overridepublic boolean isRemoting(Object bean, String beanName) throws FrameworkException {//判断是否是服务调用方或者是否是服务提供方return isReference(bean, beanName) || isService(bean, beanName);}

利用 allRemotingParsers 来解析远程 bean 。allRemotingParsers是在:initRemotingParser() 中调用EnhancedServiceLoader.loadAll(RemotingParser.class) 动态进行 RemotingParser 子类的加载,即 SPI 加载机制。

如果想扩展,比如实现一个feign远程调用的解析类,只要把RemotingParser相关实现类写在 SPI 的配置中就可以了,扩展性很强。

RemotingDesc 事务流程需要的远程 bean 的一些具体信息,比如 targetBean、interfaceClass、interfaceClassName、protocol、isReference等等。

3.TCC资源注册

public RemotingDesc parserRemotingServiceInfo(Object bean, String beanName) {RemotingDesc remotingBeanDesc = getServiceDesc(bean, beanName);if (remotingBeanDesc == null) {return null;}remotingServiceMap.put(beanName, remotingBeanDesc);Class<?> interfaceClass = remotingBeanDesc.getInterfaceClass();Method[] methods = interfaceClass.getMethods();if (isService(bean, beanName)) {try {//service bean, registry resourceObject targetBean = remotingBeanDesc.getTargetBean();for (Method m : methods) {TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class);if (twoPhaseBusinessAction != null) {TCCResource tccResource = new TCCResource();tccResource.setActionName(twoPhaseBusinessAction.name());tccResource.setTargetBean(targetBean);tccResource.setPrepareMethod(m);tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod());tccResource.setCommitMethod(ReflectionUtil.getMethod(interfaceClass, twoPhaseBusinessAction.commitMethod(),new Class[] {BusinessActionContext.class}));tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod());tccResource.setRollbackMethod(ReflectionUtil.getMethod(interfaceClass, twoPhaseBusinessAction.rollbackMethod(),new Class[] {BusinessActionContext.class}));//registry tcc resourceDefaultResourceManager.get().registerResource(tccResource);}}} catch (Throwable t) {throw new FrameworkException(t, "parser remoting service error");}}if (isReference(bean, beanName)) {//reference bean, TCC proxyremotingBeanDesc.setReference(true);}return remotingBeanDesc;}

首先判断是否是事务参与方,如果是,拿到 RemotingDesc 中的 interfaceClass,遍历接口中的方法,判断方法上是否有@TwoParserBusinessAction 注解,如果有,把参数封装成 TCCRecource,通过 DefaultResourceManager 进行 TCC 资源的注册。

这里 DefaultResourceManager 会根据 Resource 的 BranchType 来寻找对应的资源管理器,TCC 模式下资源管理类,在 tcc 模块中。

这个 rpc 解析类主要提供给 spring 模块进行使用。parserRemotingServiceInfo() 被封装到了 spring 模块的 TCCBeanParserUtils 工具类中。spring 模块的 GlobalTransactionScanner 在项目启动的时候,通过工具类解析 TCC bean,工具类 TCCBeanParserUtils 会调用 TCCResourceManager 进行资源的注册,并且如果是全局事务的服务提供者,会织入 TccActionInterceptor 代理。这些个流程是 spring 模块的功能,tcc 模块是提供功能类给 spring 模块使用。

三 .tcc 资源管理器

TCCResourceManager 负责管理 TCC 模式下资源的注册、分支的注册、提交、和回滚。

1.在项目启动时, spring 模块的 GlobalTransactionScanner 扫描到 bean 是 tcc bean 时,会本地缓存资源,并向 server 注册:

    @Overridepublic void registerResource(Resource resource) {TCCResource tccResource = (TCCResource)resource;tccResourceCache.put(tccResource.getResourceId(), tccResource);super.registerResource(tccResource);}

与server通信的逻辑被封装在了父类 AbstractResourceManage 中,这里根据 resourceId 对 TCCResource 进行缓存。父类 AbstractResourceManage 注册资源的时候,使用 resourceGroupId + actionName,actionName 就是 @TwoParseBusinessAction 注解中的 name,resourceGroupId 默认是 DEFAULT。

2.事务分支的注册在 rm-datasource 包下的 AbstractResourceManager 中,注册时参数 lockKeys 为 null,和 AT 模式下事务分支的注册还是有些不一样的。

3.分支的提交或者回滚:

    @Overridepublic BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,String applicationData) throws TransactionException {TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);if (tccResource == null) {throw new ShouldNeverHappenException("TCC resource is not exist, resourceId:" + resourceId);}Object targetTCCBean = tccResource.getTargetBean();Method commitMethod = tccResource.getCommitMethod();if (targetTCCBean == null || commitMethod == null) {throw new ShouldNeverHappenException("TCC resource is not available, resourceId:" + resourceId);}try {boolean result = false;//BusinessActionContextBusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,applicationData);Object ret = commitMethod.invoke(targetTCCBean, businessActionContext);if (ret != null) {if (ret instanceof TwoPhaseResult) {result = ((TwoPhaseResult)ret).isSuccess();} else {result = (boolean)ret;}}return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable;} catch (Throwable t) {LOGGER.error(msg, t);throw new FrameworkException(t, msg);}}

通过参数 xid、branchId、resourceId、applicationData 恢复业务的上下文 businessActionContext。

根据获取到的上下文通过反射执行 commit 方法,并返回执行结果。回滚方法类似。

这里 branchCommit() 和 branchRollback() 提供给 rm 模块资源处理的抽象类 AbstractRMHandler 调用,这个 handler 是 core 模块定义的模板方法的进一步实现类。和 registerResource() 不一样,后者是 spring 扫描时主动注册资源。

四 . tcc 模式事务处理

spring 模块中的 TccActionInterceptor 的 invoke() 方法在被代理的 rpc bean 被调用时执行。该方法先获取 rpc 拦截器透传过来的全局事务 xid ,然后 TCC 模式下全局事务参与者的事务流程还是交给 tcc 模块 ActionInterceptorHandler 处理。

也就是说,事务参与者,在项目启动的时候,被代理。真实的业务方法,在 ActionInterceptorHandler 中,通过回调执行。

    public Map<String, Object> proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,Callback<Object> targetCallback) throws Throwable {Map<String, Object> ret = new HashMap<String, Object>(4);//TCC nameString actionName = businessAction.name();BusinessActionContext actionContext = new BusinessActionContext();actionContext.setXid(xid);//set action anmeactionContext.setActionName(actionName);//Creating Branch RecordString branchId = doTccActionLogStore(method, arguments, businessAction, actionContext);actionContext.setBranchId(branchId);//set the parameter whose type is BusinessActionContextClass<?>[] types = method.getParameterTypes();int argIndex = 0;for (Class<?> cls : types) {if (cls.getName().equals(BusinessActionContext.class.getName())) {arguments[argIndex] = actionContext;break;}argIndex++;}//the final parameters of the try methodret.put(Constants.TCC_METHOD_ARGUMENTS, arguments);//the final resultret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute());return ret;}

这里有两个重要操作:

1.doTccActionLogStore() 这个方法中,调用了两个比较重要的方法:
fetchActionRequestContext(method, arguments),这个方法把被 @BusinessActionContextParam 注解的参数取出来,在下面的 init 方法中塞入 BusinessActionComtext ,同时塞入的还有事务相关参数。
DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,applicationContextStr, null),这个方法执行 TCC 模式下事务参与者事务分支的注册。

2.回调执行 targetCallback.execute() ,被代理的 bean 具体的业务,即 prepare() 方法。

五 .总结

tcc模块,主要提供以下功能 :

  1. 定义两阶段协议注解,提供 tcc 模式下事务流程需要的属性。
  2. 提供解析不同 rpc 框架 remoting bean 的 ParserRemoting 实现,供 spring 模块调用。
  3. 提供 TCC 模式下资源管理器,进行资源注册、事务分支注册提交回滚等。
  4. 提供 TCC 模式下事务流程的处理类,让 MethodInterceptor 代理类不执行具体模式的事务流程,而是下放到 tcc 模块。

五 .相关

作者:赵润泽,系列地址。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux——进程间通信一(共享内存、管道、systrem V)

一、进程间通信介绍 1.1、进程间通信的概念和意义 进程间通信(IPC interprocess communication)是一组编程接口&#xff0c;让不同进程之间相互传递、交换信息(让不同的进程看到同一份资源) 数据传输:一个进程需要将它的数据发送给另外一个进程 资源共享:多个进程之间共享同样…

如何选择一家适合自己的商城源码?

商城源码的选择取决于多个因素&#xff0c;包括商城的功能需求、稳定性、易用性、可定制性以及价格等。启山智软作为在市场上被广泛认可且表现优异的商城源码提供商&#xff0c;具有以下的特点和优势&#xff1a; 特点①&#xff1a;国内知名的B2B2C开源商城源码系统&#xff…

Golang | Leetcode Golang题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; func _rob(nums []int) int {first, second : nums[0], max(nums[0], nums[1])for _, v : range nums[2:] {first, second second, max(firstv, second)}return second }func rob(nums []int) int {n : len(nums)if n 1 {return nums[0]}…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

vue事件处理v-on或@

事件处理v-on或 我们可以使用v-on指令&#xff08;简写&#xff09;来监听DOM事件&#xff0c;并在事件触发时执行对应的Javascript。用法&#xff1a;v-on:click"methodName"或click"hander" 事件处理器的值可以是&#xff1a; 内敛事件处理器&#xff1…

如何让代码兼容 Python 2 和 Python 3?Future 库助你一臂之力

目录 01Future 是什么? 为什么选择 Future? 安装与配置 02Future 的基本用法 1、兼容 print 函数 2、兼容整数除法 3、兼容 Unicode 字符串 03Future 的高级功能 1. 处理字符串与字节 2. 统一异常处理…

基于SpringBoot的校园台球厅人员与设备管理系统

本系统是要设计一个校园台球厅人员与设备管理系统&#xff0c;这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…

LRU缓存算法设计

LRU 缓存算法的核⼼数据结构就是哈希链表&#xff0c;双向链表和哈希表的结合体。这个数据结构⻓这样&#xff1a; 创建的需要有两个方法&#xff0c;一个是get方法&#xff0c;一个是put方法。 一些问题&#xff1a;为什么需要使用双向链表呢&#xff1f;因为删除链表的本身&…

第一节 网络安全概述

一.网络空间安全 网络空间&#xff1a;一个由信息基础设施组成相互依赖的网络。 ---- 海陆空天&#xff08;大海、陆 地、天空、航天&#xff09; 通信保密阶段 ---- 计算机安全 ----- 信息系统安全 ----- 网络空间安全 计算机安全&#xff1a;开始秉持着“严于律己&#x…

《安富莱嵌入式周报》第339期:单片机运行苹果早期Mac系统模拟器,2GHz示波器有源探头,下一代矩阵开关面包板,卡片式声音分贝器,HP经典示波器,ReRAM

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版 https://www.bilibili.com/video/BV1Kf421Q7Lh 《安富莱嵌入式周报》第339期&#xff1a;单片机运行苹果早期Ma…

Linux开发讲课33---线程实现与线程控制步骤简析

线程概述 进程是系统中程序执行和资源分配的基本单位。 每个进程都拥有自己的数据段、代码段和堆栈段&#xff0c;这就造成了进程在进行切换等操作时都需要有比较负责的上下文切换等动作。为了进一步减少处理机的空转时间支持多处理器和减少上下文切换开销&#xff0c;进程在演…

IDEA安装IDE Eval Reset插件,30天自动续期,无限激活

第一步&#xff1a; 下载idea 注意&#xff1a;版本要是2021.2.2以下 第二步&#xff1a;快捷键CtrlAlts打开设置 第三步&#xff1a;打开下图中蓝色按钮 第四步&#xff1a;点击弹窗的 “” &#xff0c;并输入 plugins.zhile.io 点击 “ok” 第五步&#xff1a;搜索IDE Ea…

【文献解析】一种像素级的激光雷达相机配准方法

大家好呀&#xff0c;我是一个SLAM方向的在读博士&#xff0c;深知SLAM学习过程一路走来的坎坷&#xff0c;也十分感谢各位大佬的优质文章和源码。随着知识的越来越多&#xff0c;越来越细&#xff0c;我准备整理一个自己的激光SLAM学习笔记专栏&#xff0c;从0带大家快速上手激…

搭建论坛和mysql数据库安装和php安装

目录 概念 步骤 安装mysql8.0.30 安装php 安装Discuz 概念 搭建论坛的架构&#xff1a; lnmpDISCUZ l 表示linux操作系统 n 表示nginx前端页面的web服务 m 表示 mysql 数据库 用来保存用户和密码以及论坛的相关内容 p 表示php 动态请求转发的中间件 步骤 &#xff…

Explore Synapse

rm -r dp-203 -f git clone https://github.com/MicrosoftLearning/dp-203-azure-data-engineer dp-203 cd dp-203/Allfiles/labs/01 ./setup.ps1 -- This is auto-generated code SELECTTOP 100 * FROMOPENROWSET(BULK https://datalakexxxxxxx.dfs.core.windows.net/fil…

分布式共识算法

分布式的基石 分布式共识算法 前置知识&#xff1a;分布式的 CAP 问题&#xff0c;在事务一章中已有详细介绍。 正式开始探讨分布式环境中面临的各种技术问题和解决方案以前&#xff0c;我们先把目光从工业界转到学术界&#xff0c;学习两三种具有代表性的分布式共识算法&…

Python 编程快速上手——让繁琐工作自动化(第2版)读书笔记01 Python基础快速过关

Python 编程快速上手——让繁琐工作自动化&#xff08;第2版&#xff09;读书笔记01 Python基础快速过关 1 python基础概念 Python提供了高效的高级数据结构&#xff0c;还能简单有效地面向对象编程。 python运算符顺序 **——%——//——/——*——-——python中常见的数据…

Linux防火墙使用(firewalld与iptables)

防火墙概述 防火墙是一种由硬件和软件组合而成&#xff0c;在内部网和外部网之间、专有网和公共网之间构造的保护屏障&#xff0c;用以保护用户资料和信息安全的一种技术 防火墙作用在于及时发现并处理计算机网络运行时可能存在的安全风险、数据传输等问题&#xff0c;从而实现…

【游戏客户端】大话版本slg玩法正式上线~~

【游戏客户端】制作率土之滨Like玩法 大家好&#xff0c;我是Lampard家杰~~ 好久好久没有更新博客了&#xff0c;有不少大佬都在后台私信我催更&#xff0c;但是很悲伤这段时间都忙的不行QAQ 那在忙什么呢&#xff1f;就是在制作一个SLG类的玩法【帮派纷争】啦 &#xff0c;布…

数据结构+算法-实现一个计算器

在学习栈的数据结构的时候讲到可以用栈来实现一个计算器的功能&#xff0c;那么这个功能是如何实现的呢&#xff1f; 采用栈模拟得方式来实现一个计算器 要实现如下的功能: 字符串如何转为整数 2.处理加减法 如何处理加减法呢&#xff1f; 5-128 给第一个数字前面放一个号…