seata分布式事物设计思想

Feata是阿里巴巴的开源的分布式事物中间件,以高效并且对业务0浸入的方式,解决微服务场景下面临的分布式事物问题。

文档资料

Seata发展历程

  • 2014年,阿里中间件团队发布TXC(Taobao Transaction Constructor),为集团内容应用提供分布式事物服务
  • 2016年,TXC经过产品化改造,以GTS(Global Transtction Service)身份登录阿里云,成为业界唯一一款云上分布式事物产品。
  • 2019年1月开始基于TXC/GTS开源FESCAR(Fast & Easy Commit And Rollback)
  • Fescar开源后,蚂蚁金服加入Fescar社区参与共建,并在Fescar0.4.0版本中贡献TCC模式

​ 为了打造中立,更开放、生态更加丰富的分布式事物开源社区,经过社区核心成员的投票,大家决定对Fescar进行品牌升级并更名为Seata(Simple Extensible Autonomous Transaction Architecture).

Seata设计方案

既有分布式事物解决方案特点

  • 业务无侵入方案

    既有主流分布式是无解决方案中,对业务无侵入的只有XA方案,但应用XA方案又存在3个方面问题

    1.要求数据库提供对XA的支持,如mysql5.7以前的版本数据库都不能使用

    2.受协议本身的约束,事物资源的锁定周期长,这种长时间的锁定资源从业务层面来说是不必要的,因为在XA模型中事物资源的管理器就是数据库本身,所以基于XA的应用往往性能会比较差,而且难于优化。

  • 侵入业务方案

    最初分布式事物只有XA这个唯一方案,但是在实践过程中,由于业务的不断发展和以上业务无侵入一些弊端,就产生了一种从业务层面来着手解决分布式事物问题如

    1.基于可靠消息的最终一致性方案

    2.TCC

    3.Saga

​ 但是这些方案要求在应用的业务层面吧分布式事物技术约束考虑到设计中,通常每一个服务都需要设计实现正向和反向的幂等处理,这样约束设计,往往会导致很高的研发和维护成本

一个理想的分布式事物解决方案应该:像使用本地事物一样简单,业务逻辑只关注业务层面的需求,不需要考虑事物机制上的约束。

Seata原理和设计

下面引用一个官网的图来看看期设计中各个角色分工

mark

  • Transaction Manager(TM):控制全局事物的边界,负责开启一个全局事物,并最终发起全局提交和全局回滚的决议
  • Transaction Manager(RM):控制分支事物,负责分支注册,状态汇报,并接受事物协调器TC的指令,驱动分支(本地)事物的提交和回滚
  • Transaction Coordinator(TC):事物协调器,维护全局事物的运行状态,负责协调并驱动全局事物的提交和回滚

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

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

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

3.RM向TC注册分支事物,将其纳入XID对应全局事物的管辖

4.TM向TC发起针对XID全局提交和回滚决议

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

mark

与XA的差别

mark

XA的方案RM实际上是在数据库层,RM本质上就是数据库本身(通过提供支持XA的驱动程序来供应用使用)

Seata的RM是以二方包的形式作为中间件层部署在应用程序这一侧,不依赖与数据库本身对协议的支持,当然也不需要数据库支持XA协议。这点对于微服务化的架构是非常重要的,应用层不需要为本地事物和分布式事物两类不同的场景来适配两套不同的数据库驱动。

这个设计好处,剥离了分布式事物方案对数据库在协议支持上要求

二阶段提交设计不同

XA中Phase2的决议是commit还是rollback,事物性资源的锁都需要保持到Phase2完成释放

Seata中二阶段提交设计

  • 分支事物中数据的本地锁有本地事物管理,在分支事物Phase1结束时释放
  • 同时,随着本地事物的结束,连接也得到释放
  • 分支事物中数据的全局锁在事物协调器侧管理,在决议Phase2全局提交时,全局锁马上可以释放,只有在决议全局回滚的请款下,全局锁才被持有至分支的Phase2结束

这个设计机打的减少分支事物对资源(数据和连接)的锁定时间,给整体并发和吞吐的提升提供了基础

以上两点设计特性也很好的解决了既有分布式事物解决方案中短板。

此时可能会有疑问?

Phase1阶段即提交的情况下,Phase2如何回滚

我们知道Seata的JDBC数据源的代理,也就是Seata的RM

Phase1:

Seata的JDBC数据源的代理通过对业务SQL的解析,把业务数据在更新前后的数据镜像组成成回滚日志,然后将数据更新和回滚日志写入在同一个本地事物中提交。这样可以保证任务业务数据的更新一定有相应的回滚日志存在。

基于这样的机制保证,分支的本地事物便可以在全局事物的Phase1提交,马上释放本地事物锁定的资源。

Phase2:

  • 如果决议是全局提交,此时分支事物已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2可以非常快速的完成
  • 如果决议是全局回滚,RM收到协调器发来的回滚请求,通过XID和BranchID找到相应的回滚日志记录,通过回滚记录生成反向的更新sql并执行,以完成分支的回滚

Seata适用场景

使用Seata的前提条件:

  • 分支事物中涉及的资源,必须是支持ACID事物的关系型数据库。
  • 分支的提交和回滚机制,都依赖于本地事物的保障,所以应用使用的数据库必须是支持事物的

局限性:

  • 事物的隔离级别最高支持读已提交的水平
  • sql的解析还不能涵盖全部语法等

工作 模式:

  • AT(Automatic Transaction) 原生的工作模式自动提交
  • MT(Manual Transaction)模式,这种模式下,分支事物需要应用自己来定义业务本身及提交和回滚的逻辑
  • 混合模式

AT模式下本地锁和全局锁设计

整体机制

  • Phase1:业务数据和回滚日志记录在同一个本地事物中提交,释放本地资源和连接资源
  • Phase2:
    • 提交异步化,非常快速的完成
    • 回滚通过一阶段的回滚日志进行反向补偿。

写隔离

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

如:两个全局事物tx1和tx2,分别对a表的m字段进行更新操作

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

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

如果tx1二阶段全局提交,释放了全局锁,tx2拿到全局锁提交本地事物。

mark

回滚情况:

如果tx1的二阶段全局回滚,则tx1需要重新获取该数据的本地锁,进行反向补偿更新操作,实现分支的回滚。

此时,如果tx2仍在等待该数据全局锁,同时持有本地锁,则tx1的分支获取不到本地锁就回滚会失败,分支的回滚会一直重试,知道tx2的全局锁等待超时,放弃全局锁并回滚本地事物释放本地锁,tx1分支回滚最终成功。

因为整个过程全局锁在tx1结束前一直是被tx1持有的,所以不会发生脏写的问题。

mark