Logo
分布式事务

分布式事务 #

背景 #

数据库事务需要满足 ACID(原子性、一致性、隔离性、持久性)四个特性。

  • 原子性(Atomicity)指事务作为整体来执行,要么全部执行,要么全不执行;
  • 一致性(Consistency)指事务应确保数据从一个一致的状态转变为另一个一致的状态;
  • 隔离性(Isolation)指多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
  • 持久性(Durability)指已提交的事务修改数据会被持久保存。 在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为本地事务。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。但是在基于微服务的分布式应用环境下,越来越多的应用场景要求对多个服务的访问及其相对应的多个数据库资源能纳入到同一个事务当中,分布式事务应运而生。关系型数据库虽然对本地事务提供了完美的 ACID 原生支持。但在分布式的场景下,它却成为系统性能的桎梏。如何让数据库在分布式场景下满足 ACID 的特性或找寻相应的替代方案,是分布式事务的重点工作。

事务分类 #

本地事务 #

在不开启任何分布式事务管理器的前提下,让每个数据节点各自管理自己的事务。它们之间没有协调以及通信的能力,也并不互相知晓其他数据节点事务的成功与否。本地事务在性能方面无任何损耗,但在强一致性以及最终一致性方面则力不从心。

两阶段提交 #

XA 协议最早的分布式事务模型是由 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing (DTP) 模型,简称 XA 协议。基于 XA 协议实现的分布式事务对业务侵入很小。它最大的优势就是对使用方透明,用户可以像使用本地事务一样使用基于XA 协议的分布式事务。XA 协议能够严格保障事务 ACID 特性。严格保障事务 ACID 特性是一把双刃剑。事务执行在过程中需要将所需资源全部锁定,它更加适用于执行时间确定的短事务。对于长事务来说,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退明显。因此,在高并发的性能至上场景中,基于 XA 协议的分布式事务并不是最佳选择。

柔性事务 #

如果将实现了 ACID 的事务要素的事务称为刚性事务的话,那么基于 BASE 事务要素的事务则称为柔性事务。BASE 是基本可用、柔性状态和最终一致性这三个要素的缩写。

  • 基本可用(Basically Available)保证分布式事务参与方不一定同时在线;
  • 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉;
  • 最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。

在 ACID 事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。基于 ACID 的强一致性事务和基于 BASE 的最终一致性事务都不是银弹,只有在最适合的场景中才能发挥它们的最大长处。可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。

本地事务两(三)阶段事务柔性事务
业务改造实现相关接口
一致性不支持支持最终一致
隔离性不支持支持业务方保证
并发性能无影响严重衰退略微衰退
适合场景业务方处理不一致短事务&低并发长事务&高并发

核心概念 #

XA 事务 #

两阶段事务提交采用的是 X/OPEN 组织所定义的 DTP 模型所抽象的 AP(应用程序), TM(事务管理器)和 RM(资源管理器)概念来保证分布式事务的强一致性。其中 TM 与 RM 间采用 XA 的协议进行双向通信。与传统的本地事务相比,XA 事务增加了准备阶段,数据库除了被动接受提交指令外,还可以反向通知调用方事务是否可以被提交。TM 可以收集所有分支事务的准备结果,并于最后进行原子提交,以保证事务的强一致性。下图为两阶段提交模型

两阶段提交模型

Java 通过定义 JTA 接口实现了 XA 模型,JTA 接口中的 ResourceManager 需要数据库厂商提供 XA 驱动实现,Transaction‑Manager 则需要事务管理器的厂商实现,传统的事务管理器需要同应用服务器绑定,因此使用的成本很高。而嵌入式的事务管器可以通过 jar 形式提供服务,同 DBPlusEngine 集成后,可保证分片后跨库事务强一致性。通常,只有使用了事务管理器厂商所提供的 XA 事务连接池,才能支持 XA 的事务。DBPlusEngine 在整合 XA 事务时,采用分离 XA 事务管理和连接池管理的方式,做到对应用程序的零侵入。

柔性事务 #

柔性事务在 2008 年发表的一篇论文中被最早提到,它提倡采用最终一致性放宽对强一致性的要求,以达到事务处理并发度的提升。TCC 和 Saga 是两种常见实现方案。他们主张开发者自行实现对数据库的反向操作,来达到数据在回滚时仍能够保证最终一致性。SEATA 实现了 SQL 反向操作的自动生成,可以使柔性事务不再必须由开发者介入才能使用。DBPlusEngine 集成了 SEATA 作为柔性事务的使用方案。

XA事务 #

XAShardingSphereTransactionManager 为 DBPlusEngine 的分布式事务的 XA 实现类。它主要负责对多数据源进行管理和适配,并且将相应事务的开启、提交和回滚操作委托给具体的 XA 事务管理器。

XA 事务

开启全局事务 #

收到接入端的 set autoCommit=0 时,XAShardingSphereTransactionManager 将调用具体的 XA 事务管理器开启 XA 全局事务,以 XID 的形式进行标记。

执行真实分片 SQL #

XAShardingSphereTransactionManager 将数据库连接所对应的 XAResource 注册到当前 XA 事务中之后,事务管理器会在此阶段发送 XAResource.start 命令至数据库。数据库在收到 XAResource.end 命令之前的所有 SQL 操作,会被标记为 XA 事务。

例如:

XAResource1.start ## Enlist 阶段执行
statement.execute("sql1"); ## 模拟执行一个分片 SQL1
statement.execute("sql2"); ## 模拟执行一个分片 SQL2
XAResource1.end ## 提交阶段执行

示例中的 sql1 和 sql2 将会被标记为 XA 事务。

提交或回滚事务 #

XAShardingSphereTransactionManager 在接收到接入端的提交命令后,会委托实际的 XA 事务管理进行提交动作,事务管理器将收集到的当前线程中所有注册的 XAResource,并发送 XAResource.end 指令,用以标记此 XA 事务边界。接着会依次发送 prepare 指令,收集所有参与 XAResource 投票。若所有 XAResource 的反馈结果均为正确,则调用 commit 指令进行最终提交;若有任意 XAResource 的反馈结果不正确,则调用 rollback 指令进行回滚。在事务管理器发出提交指令后,任何XAResource 产生的异常都会通过恢复日志进行重试,以保证提交阶段的操作原子性,和数据强一致性。

XAResource1.prepare ## ack: yes
XAResource2.prepare ## ack: yes
XAResource1.commit
XAResource2.commit
XAResource1.prepare ## ack: yes
XAResource2.prepare ## ack: no
XAResource1.rollback
XAResource2.rollback

Seata 柔性事务 #

柔性事务在 2008 年发表的一篇论文中被最早提到, 它提倡采用最终一致性放宽对强一致性的要求,以达到事务处理并发度的提升。

TCC 和 Saga 是两种常见实现方案。 他们主张开发者自行实现对数据库的反向操作,来达到数据在回滚时仍能够保证最终一致性。 SEATA 实现了 SQL 反向操作的自动生成,可以使柔性事务不再必须由开发者介入才能使用。

DBPlusEngine 集成了 SEATA 作为柔性事务的使用方案。

整合 Seata AT 事务时,需要将 TM,RM 和 TC 的模型融入 DBPlusEngine 的分布式事务生态中。在数据库资源上,Seata通过对接 DataSource 接口,让 JDBC 操作可以同 TC 进行远程通信。同样,DBPlusEngine 也是面向 DataSource 接口,对用户配置的数据源进行聚合。因此,将 DataSource 封装为基于 Seata 的 DataSource 后,就可以将 Seata AT 事务融入到 DBPlusEngine 的分片生态中。

Seata 柔性事务

引擎初始化 #

包含 Seata 柔性事务的应用启动时,用户配置的数据源会根据 seata.conf 的配置,适配为 Seata 事务所需的 DataSourceProxy,并且注册至 RM 中。

开启全局事务 #

TM 控制全局事务的边界,TM 通过向 TC 发送 Begin 指令,获取全局事务 ID,所有分支事务通过此全局事务 ID,参与到全局事务中;全局事务 ID 的上下文存放在当前线程变量中。

执行真实分片 SQL #

处于 Seata 全局事务中的分片 SQL 通过 RM 生成 undo 快照,并且发送 participate 指令至 TC,加入到全局事务中。由于DBPlusEngine 的分片物理 SQL 采取多线程方式执行,因此整合 Seata AT 事务时,需要在主线程和子线程间进行全局事务 ID 的上下文传递。

提交或回滚事务 #

提交 Seata 事务时,TM 会向 TC 发送全局事务的提交或回滚指令,TC 根据全局事务 ID 协调所有分支事务进行提交或回滚。