基础
事务
事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销
本地事务
本地事物其实可以认为是数据库提供的事务机制(ACID 原则)
分布式事务
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,分布式事务就是为了保证不同数据库的数据一致性
场景
- 单体系统访问多个数据库 :一个服务需要调用多个数据库实例完成数据的增删改操作
- 多个微服务调用同一个数据库:多个服务需要调用一个数据库实例完成数据的增删改查
- 多个微服务访问多个数据库
方案
2PC
两阶段提交(2PC
),对业务侵 ⼊很小,它最⼤的优势就是对使⽤⽅透明,用户可以像使⽤本地事务⼀样使⽤基于 XA 协议的分布式事务,能够严格保障事务 ACID 特性
(2PC
) 的缺点也是显而易见,它是一个强一致性的同步阻塞协议,事务执⾏过程中需要将所需资源全部锁定,也就是俗称的 刚性事务
。所以它比较适⽤于执⾏时间确定的短事务,整体性能比较差
3PC
三段提交(3PC
)是二阶段提交(2PC
)的一种改进版本 ,为解决两阶段提交协议的阻塞问题,上边提到两段提交,当协调者崩溃时,参与者不能做出最后的选择,就会一直保持阻塞锁定资源
2PC
中只有协调者有超时机制,3PC
在协调者和参与者中都引入了超时机制,协调者出现故障后,参与者就不会一直阻塞
虽然 3PC
用超时机 制,解决了协调者故障后参与者的阻塞问题,但与此同时却多了一次网络通信,性能上反而变得更差,也不太推荐
TCC
两阶段提交的一个变种,不同的是 TCC
为在业务层编写代码实现的两阶段提交。TCC
分别指 Try
、Confirm
、Cancel
,一个业务操作要对应的写这三个方法
TCC 不存在资源阻塞的问题,因为每个方法都直接进行事务的提交,一旦出现异常通过则 Cancel
来进行回滚补偿,这也就是常说的补偿性事务
原本一个方法,现在却需要三个方法来支持,TCC 对业务的侵入性很强,而且这种模式并不能很好地被复用,会导致开发量激增。还要考虑到网络波动等原因,为保证请求一定送达都会有重试机制,所以考虑到接口的幂等性
消息事务(最终一致性)
消息事务其实就是基于消息中间件的两阶段提交,将本地事务和发消息放在同一个事务里,保证本地操作和发送消息同时成功
- 订单系统向
MQ
发送一条预备扣减库存消息,MQ
保存预备消息并返回成功ACK
- 接收到预备消息执行成功
ACK
,订单系统执行本地下单操作,为防止消息发送成功而本地事务失败,订单系统会实现MQ
的回调接口,其内不断的检查本地事务是否执行成功,如果失败则rollback
回滚预备消息;成功则对消息进行最终commit
提交。 - 库存系统消费扣减库存消息,执行本地事务,如果扣减失败,消息会重新投,一旦超出重试次数,则本地表持久化失败消息,并启动定时任务做补偿
基于消息中间件的两阶段提交方案,通常用在高并发场景下使用,牺牲数据的强一致性换取性能的大幅提升,不过实现这种方式的成本和复杂度是比较高的,还要看实际业务情况
Seata
也是从两段提交演变而来的一种分布式事务解决方案,提供了 AT
、TCC
、SAGA
和 XA
等事务模式,这里重点介绍 AT
模式
Transaction Coordinator(TC)
: 全局事务协调者,用来协调全局事务和各个分支事务(不同服务)的状态, 驱动全局事务和各个分支事务的回滚或提交。Transaction Manager™
: 事务管理者,业务层中用来开启/提交/回滚一个整体事务(在调用服务的方法中用注解开启事务)。Resource Manager(RM)
: 资源管理者,一般指业务数据库代表了一个分支事务(Branch Transaction
),管理分支事务与TC
进行协调注册分支事务并且汇报分支事务的状态,驱动分支事务的提交或回滚
Seata 实现分布式事务,设计了一个关键角色 UNDO_LOG
(回滚日志记录表),我们在每个应用分布式事务的业务库中创建这张表,这个表的核心作用就是,将业务数据在更新前后的数据镜像组织成回滚日志,备份在 UNDO_LOG
表中,以便业务异常能随时回滚
第一阶段
把业务数据在更新前后的数据镜像组织成回滚日志,将业务数据的更新和回滚日志在同一个本地事务中提交,分别插入到业务表和 UNDO_LOG 表中。在本地事务提交前,各分支事务需向全局事务协调者 TC 注册分支 ( Branch Id) ,为要修改的记录申请全局锁 ,要为这条数据加锁,利用 SELECT FOR UPDATE 语句。而如果一直拿不到锁那就需要回滚本地事务。TM 开启事务后会生成全局唯一的 XID,会在各个调用的服务间进行传递。
有了这样的机制,本地事务分支(Branch Transaction
)便可以在全局事务的第一阶段提交,并马上释放本地事务锁定的资源。相比于传统的 XA
事务在第二阶段释放资源,Seata
降低了锁范围提高效率,即使第二阶段发生异常需要回滚,也可以快速从UNDO_LOG
表中找到对应回滚数据并反解析成 SQL 来达到回滚补偿
第二阶段
根据各分支的决议做提交或回滚
- 如果决议是全局提交,此时各分支事务已提交并成功,这时
全局事务协调者(TC)
会向分支发送第二阶段的请求。收到 TC 的分支提交请求,该请求会被放入一个异步任务队列中,并马上返回提交成功结果给 TC。异步队列中会异步和批量地根据Branch ID
查找并删除相应UNDO LOG
回滚记录 - 如果决议是全局回滚,过程比全局提交麻烦一点,
RM
服务方收到TC
全局协调者发来的回滚请求,通过XID
和Branch ID
找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚