什么是分布式事务
事务的概念:数据库理论之事务与恢复技术
本地事务主要限制在单个会话内,不涉及多个数据库资源。但是在基于SOA(Service-Oriented Architecture,面向服务架构)的分布式应用环境下,越来越多的应用要求对多个数据库资源,多个服务的访问都能纳入到同一个事务当中,分布式事务应运而生。
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式事务可能涉及到多个服务、多个数据源之间,因此较之基于单一数据库资源访问的本地事务,分布式事务的应用架构更为复杂。在不同的分布式应用架构下,实现一个分布式事务要考虑的问题并不完全一样,比如对多资源的协调、事务的跨服务传播等,实现机制也是复杂多变。
分布式事务相关理论
分布式事务解决方案
基于XA协议的2PC或3PC
2PC理论和过程已经在分布式一致性理论和一致性协议2PC、3PC 中描述过了。
需要注意的是2PC处理分布式事务最致命的缺点是同步阻塞,即两阶段提交中的第二阶段, 协调者需要等待所有参与者发出 yes 请求, 或者一个参与者发出 no 请求后, 才能执行提交或者中断操作. 这会造成长时间同时锁住多个资源, 造成性能瓶颈,如果参与者有一个耗时长的操作, 性能损耗会更明显,即会丢失一部分可用性。 而现在的大型分布式系统中,往往对性能较为敏感,那么2PC带来的性能损耗通常是不可忍受的。
2PC和3PC强调的都是强一致性,往往会丢失一部分可用性或数据一致性(网络分区问题)。
TCC补偿事务(Try-Confirm-Commit)
TCC 是基于补偿型事务的 AP 系统的一种实现, 具有最终一致性。
操作方法 | 含义 |
---|---|
Try | 预留业务资源/数据效验 |
Confirm | 确认执行业务操作,实际提交数据,不做任何业务检查,try成功,confirm必定成功,需保证幂等。 如果执行出现异常, 要进行重试。 |
Cancel | 取消执行业务操作,释放 Try 阶段预留的业务资源,实际回滚数据,需保证幂等。 |
其核心在于将业务分为两个操作步骤完成。不依赖 RM 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
如果任一子业务在 Try 阶段有操作无法执行成功,则调用 cancel 接口回滚try阶段进行的操作。 否则调用confirm接口开始执行业务操作(此时断言confirm操作必须成功),注意 confirm 和 cancel 两个阶段是两个相悖的选择,而不是先后关系。 如果任一子业务在 Confirm 阶段有操作无法执行成功, 会造成对业务活动管理器的响应超时,此时要对其他业务执行补偿性事务。 如果补偿操作执行也出现异常, 必须进行重试, 若实在无法执行成功, 则事务管理器必须能够感知到失败的操作, 进行log(用于事后人工进行补偿性事务操作或者交由中间件接管在之后进行补偿性事务操作)。

例如:A 要向 B 转账,思路大概是:
我们有一个本地方法,里面依次调用
1. 首先在 Try 阶段,要先调用远程接口把 B 和 A 的钱给冻结起来。
2. 如果 Try 中冻结成功,则进入 Confirm 阶段,否则调用 cancel 接口将冻结的钱解冻。
3. 在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
假设用户user表中有两个字段:可用余额、冻结余额。
- A扣钱对应服务A(ServiceA)
- B加钱对应服务B(ServiceB)
- 转账订单服务(OrderService)
- 业务转账方法服务(BusinessService)
ServiceA,ServiceB,OrderService都需分别实现try()
,confirm()
,cancle()
方法,方法对应业务逻辑如下
操作方法 | ServiceA | ServiceB | OrderService |
---|---|---|---|
try() | 校验余额(并发控制) 冻结余额+1000 余额-1000 |
冻结余额+1000 | 创建转账订单,状态待转账 |
confirm() | 冻结余额-1000 | 余额+1000 冻结余额-1000 |
状态变为转账成功 |
cancle() | 冻结余额-1000 余额+1000 |
冻结余额-1000 | 状态变为转账失败 |
其中业务调用方BusinessService中就需要调用 ServiceA.try()
,ServiceB.try()
,OrderService.try()
。可见TCC对应用程序的侵入性是比较大的,需要程序员在代码中对接口进行实现。
优点: TCC能够对分布式事务中的各个资源进行分别锁定, 分别提交与释放, 例如, 假设有A、B两个操作, 假设A操作耗时短, 那么 A 就能较快的完成自身的try-confirm-cancel流程, 释放资源,即可用性有较大提高。 无需等待 B 操作. 如果事后出现问题, 追加执行补偿性事务即可。
缺点: TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
本地消息表(异步确保)
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,该实现方式遵循BASE理论,也是属于确保最终一致性的补偿性事务,性能较高。

基本思路就是:
消息生产方需要在业务表的同一数据库中额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个本地事务里提交。 然后消息会经过MQ发送到消息的消费方(消息状态确认系统定时扫描消息表中的消息状态)。如果消息发送失败,会进行重试发送。
消息消费方需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET 中有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
MQ事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
- MQ 发送方发送远程事务消息到 MQ Server;
- MQ Server 给予响应, 表明事务消息已成功到达 MQ Server.
- MQ 发送方 Commit 本地事务.
- 若本地事务 Commit 成功, 则通知 MQ Server 允许对应事务消息被消费; 若本地事务失败,则通知 MQ Server 对应事务消息应被丢弃。若 MQ 发送方超时未对 MQ Server 作出本地事务执行状态的反馈, 那么需要 MQ Server 向 MQ 发送方主动回查事务状态, 以决定事务消息是否能被消费。
- 当得知本地事务执行成功时, MQ Server 允许 MQ 订阅方消费本条事务消息。需要额外说明的一点, 就是事务消息投递到 MQ 订阅方后, 并不一定能够成功执行. 需要MQ 订阅方主动给予消费反馈(ack)。如果 MQ 订阅方执行远程事务成功, 则给予消费成功的 ack, 那么 MQ Server 可以安全将事务消息移除;如果执行失败, MQ Server 需要对消息重新投递, 直至消费成功。
也就是说在业务方法内要向消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check
接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 目前主流MQ中只有RocketMQ支持事务消息。
最大努力通知型(基于消息中间件,定期校对)
这是分布式事务中要求最低的一种,甚至连最终一致性都不一定能保证。它也可以通过消息中间件实现, 与前面异步确保型操作不同的一点是, 在消息由 MQ Server 投递到消费者之后, 允许在达到最大重试次数之后正常结束事务。
流程:
- 业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。
- 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知 N 次后不再通知。
- 主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
- 业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。
- 如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息。
Seata分布式事务
Seata官方文档:https://seata.io/zh-cn/docs/overview/what-is-seata.html
Seata介绍
2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback)
,和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。
Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC 模式。
为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对 Fescar 进行品牌升级,并更名为 Seata
,意为:Simple Extensible Autonomous Transaction Architecture
,是一套一站式分布式事务解决方案。
短短一年多时间,截至今日 Seata 版本已从最初的v0.1.0
开发至 v1.2.0
,社区活跃度非常高。经历过阿里内部生产经验的考核,可以说是一款十分成熟的分布式事务框架了。
Seata中的三种模式
解决分布式事务问题,有两个设计初衷:
- 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
- 高性能:减少分布式事务解决方案所带来的性能消耗
截至今日,seata中有三种分布式事务实现方案:AT、TCC和Saga
AT模式
主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题TCC模式
主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题Saga模式
是SEATA提供的长事务解决方案,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
AT模式是2PC模式的改进:
+ 一阶段:业务数据和回滚日志记录在同一个本地事务中提交(也就是说在业务表所在的数据库下需要建立一张回滚日志表),释放本地锁和连接资源。
+ 二阶段:
+ 提交异步化,非常快速地完成。
+ 回滚通过一阶段的回滚日志进行反向补偿。
具体描述请参考官网。
Seata术语
- 事务协调者 (TC, Transaction Coordinator): 维护全局和分支事务的状态,驱动全局事务提交或回滚。
- 事务管理器 (TM, Transaction Manager): 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- 资源管理器 (RM, Resource Manager): 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

一次分布式事务的处理过程
Transaction ID (XID): 全局唯一的事务id

- TM 向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
- XID在微服务调用链路的上下文中传播;
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
- TM向TC发起针对XID的全局提交或回滚决议;
- TC调度XID下管辖的全部分支事务完成提交或回滚请求。
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e5%88%86%e5%b8%83%e5%bc%8f%e4%ba%8b%e5%8a%a1%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88%e5%92%8cseata%e7%9a%84%e4%bb%8b%e7%bb%8d/