分布式事务框架seata原理

分布式事务框架seata原理

Seata简介

Seata(原名Fescar) 是阿里18年开源的分布式事务的框架。Fescar的开源对分布式事务框架领域影响 很大。作为开源大户,Fescar来自阿里的GTS,经历了好几次双十一的考验,一经开源便颇受关注。同时Fescar也保留了接近0业务入侵的优点,只需要简单的配置Fescar的数据代理和加个注解,加一个 Undolog表,就可以达到我们想要的目的。

Seata原理

seata将一个本地事务作为一个分布式事务的分支,若干个分支分布在不同的微服务上,组成一个全局事务.seata包含三个结构:

Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态 ,负责协调驱动全局事务的提交或者回滚.

Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

其中TM向TC申请一个全局事务,并生成一个全局唯一的XID.XID会在全局事务调用分支事务时在调用链路的上下文中传播,RM会携带XID向TC申请注册分支事务,TC调度XID下的所有分支事务的提交和回滚.

批注 2020-08-04 194555.jpg

一个典型的分布式事务过程:

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

  2. XID 在微服务调用链路的上下文中传播。

  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖,并会执行分支事务并提交(RM在第一阶段就已经执行了本地事务的提交或回滚),最后将执行结果汇报给TC

  4. TM根据TC中的分支事务执行情况 向 TC 发起针对 XID 的全局提交或回滚决议。

  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

Seata模式

seata提供了三种实现分布式事务的模式:AT模式,MT模式和混合模式

AT模式

业务逻辑不需要关注事务机制,分支与全局事务的交互过程自动进行。

AT****模式:主要关注多 DB 访问的数据一致性,实现起来比较简单,对业务的侵入较小。AT模式部分代码如下:不需要关注执行状态,对业务代码侵入较小。类似代码如下,只需要为方法添 加 @GlobalTransactional 注解即可。

AT模式的核心是对业务无侵入,是一种改进后的两阶段提交.

详细流程

第一阶段

第一步:解析sql语句,得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。

第二步:查询老数据,根据上面的where语句sql,去数据库查询原始的数据。

如 select * from product where name = 'TXC';得到原始的数据,如该行id=1,然后记录下来。

第三步:执行第一步的sql语句,即执行update,修改数据库的该记录的值。

第四步:查询修改后的值,select * from product where id =1.得到该行值,记录下来。

第五步:插入回滚日志,将老值、新值以及sql语句组成一个将来可用于回滚的日志,插入到UNDO_LOG表。

{
    "branchId": 641789253,
    "undoItems": [{
        "afterImage": {        //更新后的数据
            "rows": [{
                "fields": [{
                    "name": "id",
                    "type": 4,
                    "value": 1
                }, {
                    "name": "name",
                    "type": 12,
                    "value": "GTS"
                }, {
                    "name": "since",
                    "type": 12,
                    "value": "2014"
                }]
            }],
            "tableName": "product"   //数据表名
        },
        "beforeImage": {  //更新前的数据
            "rows": [{
                "fields": [{
                    "name": "id",
                    "type": 4,
                    "value": 1
                }, {
                    "name": "name",
                    "type": 12,
                    "value": "TXC"
                }, {
                    "name": "since",
                    "type": 12,
                    "value": "2014"
                }]
            }],
            "tableName": "product"   //数据表名
        },
        "sqlType": "UPDATE"  //sql语句类型
    }],
    "xid": "xid:xxx"  //全局事务id
}

第六步:向TC server注册分支,申请product表,id=1的行的全局锁。注意,这个全局锁是相对于所有可能的同时在执行的分布式事务而言的。一旦某个分支,获取了该记录的全局锁,在解锁之前,任何其他的分布式事务,不能修改该数据。

第七步:本地事务提交,将自己的本地事务、和前面的UNDO LOG一起提交。

第八步:将本地事务提交的结果上报给TC server。如成功、失败。

第二阶段

成功的情况:分支收到了TC下发的成功请求,立马返回我已OK的结果给TC,然后异步执行删除UNDO LOG的操作。因为成功了,所以用来回滚的UNDO LOG就没意义了,异步删除掉就好。

失败的情况:

1 分支收到了TC下发的失败请求,开始执行回滚逻辑。

2 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。

3 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。

4 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句。

update product set name = 'TXC' where id = 1;

5 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

结论:

可以看到,整体来说,这个分布式事务是比较迅速的,在不等待全局锁的情况下,基本和本地事务没什么区别。回滚时,也不依赖数据库本身的回滚能力,都由自己业务来实现回滚操作。

脏读和脏写

脏读:在RM提交本地事务之后,TC提交全局事务之前,由于本地事务已提交,其他事务便可以读取到已提交事务修改的数据,但在TC回滚全局事务之后,其他事务读取到的数据又会变为更新前的数据

官方给出的解决方案为:脏读取Select语句用于更新,代理方法使用@ GlobalLock + @ Transactional或@GlobalTransaction

脏写:在TC通知分支事务回滚后,分支事务回滚之前,发现涉及的数据已经被修改,无法和UNDO LOG记录中的旧数据匹配上,则会回滚失败!

官方给出的解决方案为: 脏写您必须使用@globaltransaction注意:如果要查询的业务接口不使用@globaltransactional批注,这意味着该方法不需要分布式事务,则可以在该方法上批注@ globallock + @ Transactional批注方法,然后在查询中添加for update语句。如果您的查询接口在事务链接的外边缘具有@globaltransactional批注,则只需在查询中添加for update语句即可。设计此批注的原因是,在分布式注释可用之前,分布式事务需要查询已提交的数据,而业务则不需要分布式事务。使用GlobalTransactional批注会增加一些不必要的RPC额外开销,例如开始返回xid,提交事务等。

@globaltransactional注解会开启全局事务和本地事务

@ globallock 声明事务仅执行在本地RM中,但是本次事务确保在更新状态下的操作记录不会被其他全局事务操作。即将本地事务的执行纳入seata分布式事务的管理,一起竞争全局锁,保证全局事务在执行的时候,本地业务不可以操作全局事务中的记录。

@ globallock + @ Transactional注解只会开启本地事务和获取全局锁

一阶段本地事务提交前,需要确保先拿到 全局锁 。
拿不到 全局锁 ,不能提交本地事务。
拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

举例:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。

tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345