Skip to content

seata

一、介绍

一个开源的分布式事务框架, 由阿里中间件团队发起的开源项目Fescar,后更名为Seata。seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。

中文文档地址:http://seata.io/zh-cn/

二、组件

Seata主要由三个重要组件组成:

  • TC:Transaction Coordinator 事务协调器,管理全局的分支事务的状态,用于协助全局性事务的提交和回滚。

  • TM:Transaction Manager 事务管理器,用于开启、提交或者回滚【全局事务】。

  • RM:Resource Manager 资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务

传统XA协议实现2PC方案的 RM 是在数据库层,RM本质上就是数据库自身;Seata的RM是以jar包的形式嵌入在应用程序里面

XID:TM 请求 TC 开启一个全局事务, TC 会生成一个 XID 作为该全局事务的编号XID, XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起

三、过程

一个典型的事务过程包括:

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

  2. A服务的RM向TC注册分支事务

  3. A服务执行分支事务,对数据库做操作

  4. A服务开始远程调用B服务,并把XID 在微服务调用链路的上下文中传播。

  5. B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖

  6. B服务执行分支事务,向数据库做操作

  7. 全局事务调用链处理完毕,TM 根据有无异常向 TC 发起针对 XID 的全局提交(Commit)或回滚(Rollback)决议。

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

Seata 实现分布式事务,关键角色UNDO_LOG(回滚日志记录表),每个RM所在的服务对应一个记录表

在每个应用需要分布式事务的业务库中创建这张表,这个表的核心作用是将业务数据在更新前后的数据镜像组织成回滚日志,保存在UNDO_LOG表中,以便业务异常能随时回滚

四、模式

  • AT:AT模式可以应对大多数的业务场景,并且基本可以做到无业务入侵、开发者无感知,用户只需关心自己的 业务SQL. AT 模式分为两个阶段,类似非XA协议的2PC

    • 一阶段:执行用户SQL

      1. Seata 会拦截“业务 SQL”,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据

      2. 在业务数据更新之后,再将其保存成“after image”,最后生成行锁

      3. 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

    • 二阶段:Seata框架自动生成提交或者回滚

      • 二阶段提交: 因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将阶段一保存的快照数据和行锁删掉,完成数据清理即可。

      • 二阶段回滚: 还原业务数据, 回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”。

如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理

  • TCC

  • Sage

  • XA

五、隔离级别

以mysql为例子,默认的事务隔离级是可重复读(REPEATABLE READ)级别,一般情况下,读写都是有保障了。

但是seata的AT模式依赖本地锁全局锁实现隔离,因此默认的隔离级别有所不同:

  • 分支事务通过本地锁实现了隔离,隔离级别为读已提交,即开启事务A的分支事务a1获得本地锁,一旦提交分支事务,就释放本地锁,别的事务就可以读到a1的数据,后面还有有其他的分支事务,例如a2、a3等;

此时,别的事务B可以读到脏数据(如果a1后来回滚的话,B读到的就是脏数据,如果a1不回滚,就没问题)

  • 全局事务层面来看,级别是读未提交,即 分支事务存在回滚情况,存在脏读情况;由于全局锁机制,可以保证写写安全

其他事务的写操作不会有问题,因为有全局锁!总结为一个写一个读,不冲突,但存在脏读;一个写,另一个也写,会冲突!

什么是全局锁?

全局锁其实就是一个普通表,我们称之为全局表,记录了事务A的分支事务a1、a2、a3涉及的表和该表的行,假设是表1的 2、4行,那么其他事务B去修改表1的第5行时,需要去全局表检查下第五行是否已存在, 这里是不存在的,即事务B和A不冲突,假设B尝试修改表1的第2行,那么就冲突了,禁止提交,直至事务A释放全局锁。

脏读问题

如果出现非seata管理业务恰好同时修改数据,此时还是会造成脏读问题。但是这种概率是非常小的,因为业务大多数时候是成功的,少数才会回滚。设计系统的时候也需要尽量避免不一样的事物去操作同一字段。 如果出现,需要报警,并进行人工干预。

六、ABA问题

产生问题的原因:

  1. 开发人员在使用seata的时候,对于同一张表的操作没有使用@GlobalTransactional注解覆盖到,导致了undo log被脏写;

  2. 当产生回滚时,在进行数据校验时,发现afterImage与当前数据不一致进而无法正常回滚,抛出SQLException,最终包装成BranchRollbackFailed_Retriable异常,导致seata一直重试回滚;

  3. 在数据校准后,某一刻的数据与afterImage一致,此时seata就回滚成功,形成ABA问题;

解决方式:

  • 对于同一张表使用@GlobalTransactional注解,即使是本地事务也使用这个注解。因为seata有全局锁保证

  • 本地事务使用@GlobalLock注解获取全局锁。

官方说将在1.6版本后解决seata分布式事务一直尝试回滚的问题,可以避免ABA问题的产生,后续还需要提供一些其他功能辅助开发人员回滚数据。