AT模式
AT模式是Seata分布式事务解决方案中的一种事务模式,它采用了类似于传统数据库事务的两阶段提交协议(Two-Phase Commit,2PC)来实现分布式事务的一致性。
核心机制
执行阶段:每个微服务的请求完成后,基于本地数据库的事务能力,保证业务数据和回滚日志在同一个本地事务中提交,快速释放连接和对资源的锁定;
完成阶段:全局提交时分支事务已经完成提交,会清理回滚日志,快速结束流程;全局回滚基于XID和BranchID查询回滚日志,完成数据回滚;
实现方式
在AT模式下,全局事务由一个唯一的全局事务ID(Global Transaction ID)来标识,它负责协调和管理所有的分支事务。
当一个分支事务参与全局事务时,它需要将自己注册到全局事务中,并在事务提交前进行本地的预提交操作(Prepare——一阶段),将事务的执行结果保存在本地日志中,但并不提交到数据库中。如果所有的分支事务都预提交成功,Seata会发出一个全局提交命令,要求所有的分支事务提交事务。此时,每个分支事务都会根据全局提交命令提交本地的事务,如果提交成功,则通知Seata自己已经提交成功,Seata最终会返回一个全局提交成功的结果。如果有任何一个分支事务提交失败,则Seata会发出一个全局回滚命令,要求所有的分支事务回滚事务,从而保证所有分支事务的一致性。
- TM负责定义全局事务的边界,向TC申请,开启一个全局事务;
- 全局事务创建成功后,生成全局唯一的XID;
- XID会在微服务请求链路上下文中传播;
- RM向TC注册分支事务,并归属到XID对应的全局事务进行调度;
- TM向TC发起相应XID的全局事务提交或回滚决议;
- TC完成对XID管理的全部分支事务提交或回滚的调度;
工作机制
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
AT 分支事务的业务逻辑:
1 | update product set name = 'GTS' where name = 'TXC'; |
一阶段(perpare预提交)
过程:
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = ‘TXC’)等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
1 | select id, name, since from product where name = 'TXC'; |
得到前镜像:
id | name | since |
---|---|---|
1 | TXC | 2014 |
- 执行业务 SQL:更新这条记录的 name 为 ‘GTS’。
- 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
1 | select id, name, since from product where id = 1; |
得到后镜像:
id | name | since |
---|---|---|
1 | GTS | 2014 |
插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到
UNDO_LOG
表中。回滚记录数据格式如下:包括
afterImage
前镜像、beforeImage
后镜像、branchId
分支事务ID、xid
全局事务ID
1 | { |
这样就可以保证,任何提交的业务数据的更新一定有相应的回滚日志。
在本地事务提交前,各分支事务需向
全局事务协调者
TC 注册分支 (Branch Id
) ,为要修改的记录申请 全局锁 ,要为这条数据加锁,利用SELECT FOR UPDATE
语句。而如果一直拿不到锁那就需要回滚本地事务。TM 开启事务后会生成全局唯一的XID
,会在各个调用的服务间进行传递。
有了这样的机制,本地事务分支(Branch Transaction
)便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源。相比于传统的 XA
事务在第二阶段释放资源,Seata
降低了锁范围提高效率,即使第二阶段发生异常需要回滚,也可以快速 从UNDO_LOG
表中找到对应回滚数据并反解析成 SQL 来达到回滚补偿。
- 提交前,向 TC 注册分支:申请
product
表中,主键值等于 1 的记录的 全局锁 。 - 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC。
二阶段-回滚
决议是全局回滚:
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
1 | update product set name = 'TXC' where id = 1; |
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
注意:这里删除回滚日志记录操作,一定是在本地业务事务执行之后
二阶段-提交
如果决议是全局提交,此时各分支事务已提交并成功:
全局事务协调者(TC)
会向分支发送第二阶段的请求。收到 TC 的分支提交请求,该请求会被放入一个异步任务队列中,并马上返回提交成功结果给 TC。- 异步队列中会异步和批量地根据
Branch ID
查找并删除相应UNDO LOG
回滚记录。
数据源代理
在AT模式中,应用需要使用Seata组件中的JDBC代理数据源DataSourceProxy,实现对真正目标数据源的代理访问。
应用场景
AT模式是Seata分布式事务解决方案中的一种事务模式,它适用于以下场景:
- 数据库访问:当需要同时操作多个数据库,并且需要保证这些操作的原子性时,AT模式是一种可行的选择。例如,将订单信息插入到订单表中,同时将库存信息减少,这两个操作需要保证在同一个事务中完成。
- 远程服务调用:当需要在多个服务之间调用,同时需要保证这些调用的原子性时,AT模式是一种可行的选择。例如,下订单需要调用用户服务和库存服务,需要保证这些调用的结果是一致的。
- 消息队列处理:当需要使用消息队列进行异步处理,并且需要保证这些处理的原子性时,AT模式是一种可行的选择。例如,将订单信息写入消息队列,并将库存信息更新为已扣减状态,这两个操作需要保证在同一个事务中完成。
优点
- 实现简单。AT模式只需要使用数据库本身提供的特性来实现分布式事务,不需要额外的业务代码来处理事务的提交和回滚,从而减少了业务代码的复杂度和维护成本。
- 性能高。AT模式不需要涉及全局锁和协调者,因此可以有效地提高分布式事务的性能和吞吐量。
- 容错性强。AT模式的本地事务和回滚机制可以有效地避免事务的异常和失败,从而提高了分布式事务的容错性和可用性。
- 兼容性好。AT模式可以与多种数据库和应用程序框架兼容,能够满足不同业务场景下的需求。
存在的问题
AT模式存在的问题主要包括以下几个方面:
- 依赖于应用程序的异常处理机制:AT模式通过应用程序的异常处理机制来实现事务回滚,需要开发人员自行处理分支事务的异常情况。因此,在实际应用中,需要开发人员编写大量的异常处理代码,使得代码量较大,且错误处理不够统一和规范。
- 分支事务的阻塞问题:在AT模式中,当一个分支事务处于执行过程中时,会阻塞其他分支事务的执行,导致系统的并发性能下降。这是由于AT模式中需要通过全局锁来保证事务的一致性,因此当一个分支事务获得全局锁后,其他分支事务就无法获取锁并执行。
- 回滚操作的性能问题:当一个分支事务需要回滚时,AT模式需要执行大量的UndoLog操作来将数据恢复到之前的状态。这些操作需要消耗大量的时间和系统资源,导致回滚操作的性能较低。
- 分布式事务的可扩展性问题:AT模式的实现方式需要应用程序显式地管理事务,使得系统的可扩展性受到限制。当系统需要扩展到多个节点时,需要对AT模式的代码进行大量的修改和调整,使得系统的维护和升级变得困难。
因此,在选择AT模式时,需要根据具体的业务需求和系统性能做出权衡,同时需要考虑系统的可用性和数据兼容性等问题。
对策
使用声明式事务管理:在使用Spring框架时,可以使用声明式事务管理的方式,通过在方法上添加@Transactional注解来管理事务。这样可以避免手动编写事务管理代码,简化开发流程,并提高代码的可维护性和可扩展性。
使用分布式事务管理框架:Seata是一种流行的分布式事务管理框架,可以支持多种分布式事务模式,如TCC、AT、XA等。使用Seata可以实现分布式事务的自动化管理,避免手动编写事务管理代码,并提高系统的可维护性和可扩展性。
使用异步消息机制:在AT模式中,可以将分支事务的处理异步化,通过使用消息队列等异步消息机制来处理分支事务。Seata支持使用Kafka和RocketMQ两种消息中间件来实现异步消息模式,可以通过在Seata的配置文件中配置消息中间件的相关参数来启用异步消息模式。对于Kafka和RocketMQ两种消息中间件,Seata提供了相应的插件,可以直接在Seata中使用。在异步消息模式下,Seata的全局事务管理器将分支事务的状态保存到消息中,由消息中间件进行分发和处理,从而减轻全局事务管理器的压力,提高了性能和可靠性。
- 配置消息中间件:首先需要在Seata的配置文件中配置消息中间件的相关参数,包括消息中间件类型、地址、用户名、密码等信息。
- 引入消息中间件插件:Seata支持Kafka和RocketMQ两种消息中间件,需要在Seata的classpath中引入相应的插件,例如对于Kafka,需要引入
seata-server-extensions/seata-registry/seata-registry-kafka
插件。 - 定义消息格式:在Seata中,需要定义全局事务和分支事务的消息格式,包括事务ID、分支事务ID、分支事务状态等信息。
- 启用异步消息模式:在Seata的配置文件中将
transaction.coordinator.type
配置为async-commit
,即可启用异步消息模式。
使用乐观锁机制:在数据访问时,可以使用乐观锁机制来避免使用全局锁导致的性能问题。乐观锁机制通过使用版本号等机制来实现数据的并发控制,从而提高系统的并发性能。
乐观锁机制并不会在事务访问数据时对数据进行加锁,而是在提交修改时检查版本号,因此不会造成事务阻塞和等待的情况。
在使用乐观锁机制时,每个事务在访问数据时都会读取当前的版本号,并在提交修改前检查版本号是否发生变化。如果版本号发生变化,说明有其他事务已经修改了数据,当前事务需要重新读取数据并重新执行操作;如果版本号未发生变化,则说明数据未被修改,当前事务可以提交修改。
使用分布式缓存:在AT模式中,回滚操作需要执行大量的UndoLog操作,消耗大量的时间和系统资源。通过使用分布式缓存,可以将回滚操作中的数据存储在缓存中,从而避免不必要的数据库操作,提高系统的性能。
综上所述,通过使用声明式事务管理、分布式事务管理框架、异步消息机制、乐观锁机制、分布式缓存等技术手段,可以避免AT模式中存在的问题,提高系统的性能和可维护性。
涉及到的回滚机制
在AT模式中,主要涉及到两种回滚机制:本地回滚和全局回滚。
- 本地回滚:当一个分支事务出现异常时,AT模式会自动进行本地回滚,即将分支事务中所做的修改全部撤销,将数据恢复到事务开始前的状态。
- 全局回滚:当某个分支事务执行失败并进行了本地回滚时,全局事务需要回滚,即将已经执行成功的分支事务中的修改全部撤销,将数据恢复到事务开始前的状态。在AT模式中,全局回滚是由应用程序显式调用Seata提供的API来触发的。
需要注意的是,AT模式的回滚机制是通过UndoLog实现的。每个参与者在执行分支事务前,会生成UndoLog。当分支事务需要回滚时,Seata会利用UndoLog中记录的信息来回滚事务。同时,如果某个分支事务执行失败,全局事务协调器也会收集所有分支事务的UndoLog,并根据UndoLog的内容来决定是否需要回滚全局事务。