关于全局事务的执行,虽然之前的文章中也有所涉及,但不够细致,今天再深入的看一下事务的整个执行过程是怎样的。
1. TransactionManager
io.seata.core.model.TransactionManager是事务管理器,它定义了一个全局事务的相关操作

DefaultTransactionManager是TransactionManager的一个实现类

可以看到,所有操作(开启、提交、回滚、查询状态、上报)都是调用TmNettyRemotingClient#sendSyncRequest()方法向TC发请求
2. GlobalTransaction
DefaultGlobalTransaction实现了GlobalTransaction,它代表一个全局事务

有两件事情需要留意,一是transactionManager是什么? 二是GlobalTransactionRole又是什么?


采用静态内部类的形式来构造单例,还记得DefaultRMHandler和DefaultResourceManager也都是通过静态内部类的形式构造单例

3. TransactionalTemplate
TransactionalTemplate是全局事务执行模板,所有业务逻辑都在其定义的模板方法中执行
io.seata.tm.api.TransactionalTemplate#execute()

现在整个过程清楚了,首先根据事务传播特性来创建一个事务对象,然后开启事务,执行业务逻辑处理,最后提交事务,如果业务执行过程中抛异常,则回滚事务。
现在有一个问题,什么情况下会进入TransactionalTemplate#execute(),或者说什么时候调用该方法?
要回答这个问题,又得从io.seata.spring.annotation.GlobalTransactionScanner说起,这个前面已经说过了,想了解的可以再看看之前那篇 https://www.cnblogs.com/cjsblog/p/16866796.html
从GlobalTransactionScanner说起就太长了,直接快进到GlobalTransactionalInterceptor拦截器吧
当被调用的方法上有@GlobalTransactional注解时,就会被拦截,从而进入GlobalTransactionalInterceptor#invoke(),在invoke()里会调用GlobalTransactionalInterceptor#handleGlobalTransaction(),于是顺利进入TransactionalTemplate#execute()
也就是说,当进入第一个@GlobalTransactional方法时,此时全局事务为空,于是创建一个角色为“GlobalTransactionRole.Launcher”的DefaultGlobalTransaction。当方法内部又调用了另一个@GlobalTransactional方法,于是再创建一个角色为“GlobalTransactionRole.Participant”的DefaultGlobalTransaction。以此类推,后面的都是事务“参与者”。

好了,现在事务已经创建,接下来就可以开启事务并执行业务逻辑处理了

可以看到,只有角色为“GlobalTransactionRole.Launcher”的线程才可以执行事务的开启提交回滚操作,而且这些操作的底层都是调用TransactionManager中的方法,最终是调用TmNettyRemotingClient#sendSyncRequest()方法向TC发送同步请求
最后,看一下什么时候回滚

catch捕获到异常就回滚
以上这些说的都是TM,因为是TM在控制整个全局事务的执行,至于RM本地事务的执行要看io.seata.rm.datasource.ConnectionProxy,这个在之前都讲过了

4. GlobalLockTemplate
GlobalLockTemplate是全局锁模板,是需要全局锁的本地事务的一个执行器模板



那么,在哪里用这个"TX_LOCK"线程变量呢?在BaseTransactionalExecutor#execute()


默认ConnectionContext中isGlobalLockRequire为false

现在就很清晰了,当方法上加了@GlobalLock注解后,进入GlobalLockTemplate#execute(),在当前线程上绑定局部变量TX_LOCK=true。当本地事务提交的时候,上下文(ConnectionContext)中isGlobalLockRequire为true,于是给TC发请求查询锁,如果这些数据没有被任何事务加锁,或者被当前事务加锁,则都算获取到锁了,如果被别的事务加锁了,则算获取锁失败。
总结一下锁互斥,分这么几种情况:
还有一个问题,哪些数据会被加锁呢?这就要从io.seata.rm.datasource.exec.ExecuteTemplate#execute()说起了
长话短说,什么样的数据加锁取决于数据库,以及SQL语句,自行理解一下吧



5. 总结
1、Seata到底是如何实现分布式事务的?
总之,数据源代理和全局事务扫描是seata实现分布式事务的基础,而TM做的事情就是控制事务的执行,RM做的事就是处理好本地事务的执行,TC是协调器
2、Seata实现的全局事务,它的事务隔离级别是怎样的?会不会出现脏读、幻读、不可重复读?
先看脏读,在全局事务提交之前,分支事务早已提交,因此,默认情况下,其它的事务是可以读取到当前未提交的全局事务的数据的,故而,默认情况下会发生脏读。
举个例子,假设现在有一个全局事务A还没提交,但是其中的分支事务A1已经提交,A2还在没提交,这个时候另一个全局事务B是可以读取到A1已经提交的数据的,也就是在全局事务B中读到了还未提交的全局事务A的数据,这就是脏读。
那么,如何避免脏读呢?
思路是这样的:首先要让Seata意识到这个SQL语句执行时锁,光知道需要锁还不行,还得让它在执行的时候检查是否获取到锁了。一个SELECT语句需要锁就是将其改写成SELECT ... FOR UPDATE的形式,检查锁的话@GlobalTransactional或@GlobalLock都可以办到。于是,解决版本就有两个:
综上所述,分支事务在提交前先进行分支注册获取全局锁,在全局事务提交成功后释放全局锁。此时,其它全局事务可以读取到已提交的分支事务的数据,但这是当前全局事务还未提交,于是出现脏读。办法也很简单,首先select加for update,其次业务方法加@GlobalTransactional或@GlobalLock注解。
同理,默认是可能出现幻读和不可重复读的,它俩属于是脏写,究其原因还是因为跨数据库了,seata搞了个全局锁,这就相当于将业务中几个不同的数据库看成一个数据库,全局锁就相当于这个大数据库中的行级锁,因此解决办法还是一样
不得不说,Seata真的是一个优秀的分布式事务框架

3、AT模式、TCC模式、Saga模式、XA模式的区别
AT模式是基于支持本地事务的关系型数据库
TCC模式不依赖于数据库的事务支持,另外TCC没有全局锁,也就没有锁竞争,故而效率比AT模式高
Saga模式是seata提供的长事务解决方案
XA模式以 XA 协议的机制来管理分支事务的一种事务模式
我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass
我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的
我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul
我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试
我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
//1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json
我从Ubuntu服务器上的RVM转移到rbenv。当我使用RVM时,使用bundle没有问题。转移到rbenv后,我在Jenkins的执行shell中收到“找不到命令”错误。我内爆并删除了RVM,并从~/.bashrc'中删除了所有与RVM相关的行。使用后我仍然收到此错误:rvmimploderm~/.rvm-rfrm~/.rvmrcgeminstallbundlerecho'exportPATH="$HOME/.rbenv/bin:$PATH"'>>~/.bashrcecho'eval"$(rbenvinit-)"'>>~/.bashrc.~/.bashrcrbenvversions
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称