2022年5月21日 作者 zeroheart

Seata

Seata 是什么 官方文档

对比 7 种分布式事务方案,阿里的 Seata 真香!(原理+实战) (aisoutu.com)

(26条消息) Seata入门系列(26)-Seata压力测试_云烟成雨TD的博客-CSDN博客_seata压测报告

看了 5种分布式事务方案,我司最终选择了 Seata,真香! – 掘金 (juejin.cn)

Seata 也是从两段提交演变而来的一种分布式事务解决方案,提供了 ATTCCSAGAXA 等事务模式,这里重点介绍 AT模式。

既然 Seata 是两段提交,那我们看看它在每个阶段都做了点啥?下边我们还以下单扣库存、扣余额举例。

先介绍 Seata 分布式事务的几种角色:

  • Transaction Coordinator(TC): 全局事务协调者,用来协调全局事务和各个分支事务(不同服务)的状态, 驱动全局事务和各个分支事务的回滚或提交。
  • Transaction Manager™ : 事务管理者,业务层中用来开启/提交/回滚一个整体事务(在调用服务的方法中用注解开启事务)。
  • Resource Manager(RM): 资源管理者,一般指业务数据库代表了一个分支事务(Branch Transaction),管理分支事务与 TC 进行协调注册分支事务并且汇报分支事务的状态,驱动分支事务的提交或回滚。

Seata 实现分布式事务,设计了一个关键角色 UNDO_LOG (回滚日志记录表),我们在每个应用分布式事务的业务库中创建这张表,这个表的核心作用就是,将业务数据在更新前后的数据镜像组织成回滚日志,备份在 UNDO_LOG 表中,以便业务异常能随时回滚。

第一个阶段

比如:下边我们更新 user 表的 name 字段。

update user set name = '小富最帅' where name = '程序员内点事'
复制代码

首先 Seata 的 JDBC 数据源代理通过对业务 SQL 解析,提取 SQL 的元数据,也就是得到 SQL 的类型(UPDATE),表(user),条件(where name = '程序员内点事')等相关的信息。

第一个阶段的流程图

先查询数据前镜像,根据解析得到的条件信息,生成查询语句,定位一条数据。

select  name from user where name = '程序员内点事'
复制代码
数据前镜像

紧接着执行业务 SQL,根据前镜像数据主键查询出后镜像数据

select name from user where id = 1
复制代码
数据后镜像

把业务数据在更新前后的数据镜像组织成回滚日志,将业务数据的更新和回滚日志在同一个本地事务中提交,分别插入到业务表和 UNDO_LOG 表中。

回滚记录数据格式如下:包括 afterImage 前镜像、beforeImage 后镜像、 branchId 分支事务ID、xid 全局事务ID

{
    "branchId":641789253,
    "xid":"xid:xxx",
    "undoItems":[
        {
            "afterImage":{
                "rows":[
                    {
                        "fields":[
                            {
                                "name":"id",
                                "type":4,
                                "value":1
                            }
                        ]
                    }
                ],
                "tableName":"product"
            },
            "beforeImage":{
                "rows":[
                    {
                        "fields":[
                            {
                                "name":"id",
                                "type":4,
                                "value":1
                            }
                        ]
                    }
                ],
                "tableName":"product"
            },
            "sqlType":"UPDATE"
        }
    ]
}
复制代码

这样就可以保证,任何提交的业务数据的更新一定有相应的回滚日志。

在本地事务提交前,各分支事务需向 全局事务协调者 TC 注册分支 ( Branch Id) ,为要修改的记录申请 全局锁 ,要为这条数据加锁,利用 SELECT FOR UPDATE 语句。而如果一直拿不到锁那就需要回滚本地事务。TM 开启事务后会生成全局唯一的 XID,会在各个调用的服务间进行传递。

有了这样的机制,本地事务分支(Branch Transaction)便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源。相比于传统的 XA 事务在第二阶段释放资源,Seata 降低了锁范围提高效率,即使第二阶段发生异常需要回滚,也可以快速 从UNDO_LOG 表中找到对应回滚数据并反解析成 SQL 来达到回滚补偿。

最后本地事务提交,业务数据的更新和前面生成的 UNDO LOG 数据一并提交,并将本地事务提交的结果上报给全局事务协调者 TC。

第二个阶段

第二阶段是根据各分支的决议做提交或回滚:

如果决议是全局提交,此时各分支事务已提交并成功,这时 全局事务协调者(TC) 会向分支发送第二阶段的请求。收到 TC 的分支提交请求,该请求会被放入一个异步任务队列中,并马上返回提交成功结果给 TC。异步队列中会异步和批量地根据 Branch ID 查找并删除相应 UNDO LOG 回滚记录。

如果决议是全局回滚,过程比全局提交麻烦一点,RM 服务方收到 TC 全局协调者发来的回滚请求,通过 XIDBranch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

注意:这里删除回滚日志记录操作,一定是在本地业务事务执行之后

上边说了几种分布式事务各自的优缺点,下边实践一下分布式事务中间 Seata 感受一下。

作者写的demo

Springboot-Notebook/springboot-seata-transaction at master · chengxy-nds/Springboot-Notebook (github.com)