目录
引言:
分布式事务是在分布式系统中处理跨越多个数据库或服务的一组操作的方式。它确保在整个操作链路中,所有参与服务要么一起成功执行,要么一起回滚,以保持数据的一致性和完整性。以下是关于分布式事务的详细介绍: (此系列将针对DTM中的TCC、Sage、二阶段信息,以及.NET CORE 中的NCC_CAP最终一致性分布式事务,全面讲解,请持续关注。)
1. 分布式事务的基本概念
- 事务参与者:在分布式事务中,每个参与到事务中的数据库或服务都被称为事务参与者。
- 事务协调者:通常是应用程序服务,负责协调各个事务参与者,确保它们遵循相同的事务逻辑。
- 资源管理器 (RM):资源管理器通常与事务参与者相同,负责管理和更新事务涉及的数据。
- 事务管理器 (TM):作为全局协调者,事务管理器跟踪事务的状态,决定何时提交或回滚。
2. 分布式事务的目标
- ACID 属性:分布式事务旨在实现传统的数据库事务的四大特性:
- 原子性 (Atomicity):事务作为一个单元执行,要么全部完成,要么全部不完成。
- 一致性 (Consistency):事务完成后,系统处于一致状态,满足所有的约束条件。
- 隔离性 (Isolation):并发事务之间相互独立,不会相互影响。
- 持久性 (Durability):一旦事务提交,其更改将永久保留。
3. 分布式事务面临的挑战
- CAP 理论:分布式系统面临选择一致性 (C),可用性 (A),或分区容错性 (P) 的困境,无法同时满足三者。
- 隔离级别:各种隔离级别如读未提交、读已提交、可重复读和串行化,对应不同的并发控制策略,可能导致脏读、不可重复读和幻读等问题。
4. 分布式事务的常见解决方案
- 两阶段提交 (2PC):TM先询问所有RM是否准备好提交,得到肯定回复后,再统一提交或回滚。缺点是可能导致阻塞,并且不能保证强一致性。
- 补偿事务 (Saga):长事务分为一系列短事务,每个都有一个对应的补偿操作,如果某个步骤出错,可以通过执行补偿操作回滚整个事务。
- 分布式事务协调器 (如 TCC、Seata):通过提供专门的事务协调组件来协助服务间的事务管理。
- 最终一致性:不是立即一致,但在一段延迟后确保所有数据源达到一致状态。
- 基于消息队列的事务:事务消息在消息队列中等待确认,直到所有相关的服务都成功处理了消息。
5. DTM是什么
DTM是一款开源的分布式事务管理器,解决跨数据库、跨服务、跨语言栈更新数据的一致性问题。
通俗一点说,DTM提供跨服务事务能力,一组服务要么全部成功,要么全部回滚,避免只更新了一部分数据产生的一致性问题。
6. 二阶段消息
6.1 概述
DTM提出的二阶段消息,可以完美替代现有的事务消息或本地消息表架构。无论从复杂度、便利性、性能,还是代码量,新架构都完胜现有架构方案,是这个领域的革命性架构。
二阶段消息是dtm首创的事务模式,用于替换本地事务表和事务消息这两种现有的方案。它能够保证本地事务的提交和全局事务提交是“原子的”,适合解决不需要回滚的分布式事务场景。下面我们来看看二阶段消息,如何解决这个业务场景的问题。
6.2 执行流程
6.2.1 成功流程
一般情况下,时序图中的5个步骤会正常完成,整个业务按照预期进行,全局事务完成。这里面有个新的内容需要解释一下,就是msg的提交是按照两个阶段发起的,第一阶段调用Prepare,第二阶段调用Commit,DTM收到Prepare调用后,不会调用分支事务,而是等待后续的Submit。只有收到了Submit,开始分支调用,最终完成全局事务。
6.2.2 提交后宕机流程
如果在本地事务提交之后,在发送Submit前,出现了进程Crash或者机器宕机会怎么样?这个时候DTM会在一定超时时间之后,取出只Prepare但未Submit的msg事务,调用msg事务指定的回查服务。
6.2.3 提交前宕机流程
如果在DTM收到Prepare调用后,AP在事务提交前,遇见故障宕机,那么数据库会检测到AP的连接断开,自动回滚本地事务。
后续DTM轮询取出已经超时的,只Prepare但没有Submit的全局事务,进行回查。回查服务发现本地事务已回滚,返回结果给DTM。DTM收到已回滚的结果后,将全局事务标记为失败,并结束该全局事务。
6.2.4 二阶段消息的应用
二阶段消息能够大幅降低消息最终一致性解决方案的难度,已获得广泛的应用,下面是两个典型的应用。
- 秒杀系统:该架构下单机可以轻松扛住上万个订单请求,并且保证库存数量和订单数量准确匹配
- 缓存一致性:通过二阶段消息,可以轻松保证DB与缓存的一致性,大大优于队列或订阅binlog的方案
6.2.5 回查原理剖析
前面的时序图中,以及接口中都出现了回查服务,在二阶段消息中,是复制粘贴代码自动处理的,而RocketMQ的事务消息,则是手动处理的。那么自动处理的原理是什么?
要进行回查,首先要在业务数据库实例中,建立一张独立的表,里面保存全局事务id。在处理业务事务时,会把gid写入到这张表。
当我们用gid回查时,如果能够在表中查到gid,那么说明本地事务已提交,这样就可以返回DTM,告知本地事务已提交。
当我们用gid回查时,没有在表中查到gid,那么说明本地事务未提交,此时可能的结果是两个,一是事务还在进行中,二是事务已回滚。我查了许多关于RocketMQ的资料,未找到有效的解决方案。搜到所有解决方案是,如果未查到结果,那么什么都不做,等待下一次回查,如果2分钟或者更久的回查,一直都是查不到的,那么认为本地事务已回滚。
上述这种方案有很大的问题:
- 两分钟还查不到gid,并不能认为本地事务已回滚,极端情况下,可能发生数据库故障(例如进程或磁盘卡住了ÿ