每次学习的过程中,我都会构建自己的思维模型,从一个点,引出另外的点,以点成面。接下来我们将从分布式的 CAP/BASE 理论基础去思考构建自己的分布式事务思维模型。
既然是分布式,那就离不开分布式的理论基础:CAP、BASE 理论,其中 分布式事务的本质 也是基于此理论基础进行实现的,
想象一下,你有一个超市,超市中有一台收银机(单机系统),现在要开连锁店,每个连锁店中都有自己的收银机(分布式系统),由于超市遍布不同区域,管理起来特别麻烦,所以你将连锁店中的收银机器进行联网同步商品价格的调整(做好准备发车了),你会遇到几个核心矛盾:
数据一致性 (Consistency): 你希望所有分店的商品库存和价格数据都是一模一样的。顾客无论在哪个分店查,看到的价格和库存都是统一的,不会出现A店显示有货,B店显示无货的尴尬情况。
服务可用性 (Availability): 你希望每一个分店在任何时候都能正常营业,接待顾客,不能动不动就关门盘点(服务宕机)。
分区容错性 (Partition Tolerance): 分店之间是靠网络联系的,万一某个分店和总部的网络断掉了(比如挖断了光缆),或者分店和分店之间联系不上了(网络分区),整个连锁系统不能就此瘫痪,就算价格无法同步与总部不一致,也得能继续扛下去。
在上述的矛盾中,分区容错性是必须要保障的,因为网络总会出问题,总不能网断了就不营业了,所以你只能在数据一致性 Consistency(简称 C)
和 服务可用性 Availability(简称 A)
之间二选一。
这就是 CAP 原理,它其实就告诉我们,在分布式系统里,上面这三个美好的愿望你不可能同时全部满足,就像三角形的三个角,你最多只能选择两个。
这就是我们常说的 CP模型、AP模型,也俗称 “鱼与熊掌不可兼得”
CP模型(一致性+分区容错性):为了保障所有分店数据绝对一致,我宁愿暂时让某个网络断掉的分店停止服务(不可用),直到数据同步成功。这适用于对数据准确性要求极高的场景,比如银行转账。
AP模型(可用性+分区容错性):为了保障每个分店都能随时提供服务,我允许数据暂时有一点不一致。比如某个分店网络断了,它先用自己的旧价格卖着,等网络恢复了再同步新数据。这适用于对体验要求高的场景,比如电商商品浏览、点赞收藏。
BASE 理论其实就是在 AP模型 下的实践和补充,为了保证一致性的一种妥协方式,其 本质就是为了保障数据的最终一致性,也就是允许数据短暂时间的不一致,最终都会达到一致性。好比上述我们的连锁店,网路断开只是一时的,总有修复的时候,彼时数据就会同步完成,达到与总部价格一致。
BASE 单词拆开来看,就是其 BASE 理论的实现原理
我们不强求数据时刻都强一致(Basically Available),但可以允许系统存在中间状态(Soft state),但经过一小段时间后,数据最终会达到一致(Eventually consistent)。
提示
分布式事务的本质,就是在 CP 和 AP 之间做权衡,并基于BASE理论来实现最终一致性的方案。既然知道了基础理论中只有CP 和 AP 两种模型,那对应的分布式事务的解决方案也无非是这2种:
强一致性的解决方案是基于 CAP 原理中的 CP模型 进行实现的,它要求所有参与方必须同时成功或同时失败,任意时刻查询参与全局事务的各节点的数据都是一致的,保证了数据的强一致性,但可能会牺牲一些可用性。
此方案主要 适用于对数据一致性要求高,在任意时刻都要查询到最新写入数据的场景,比如跨行转账业务
其优点是数据一致性高,在任意时刻都能够查询到最新写入的数据。不过其缺点也很明显,在写入期间不可读取,需要等待写入完成,在分布式事务未完全提交和回滚之前,应用程序不会查询到最新的数据,牺牲了性能和可用性,不适合高并发场景
DTP(Distributed Transaction Processing),即分布式事务处理模型,是由X/Open组织(后来的Open Group)定义的一套标准架构。它定义了在分布式系统中,多个资源(如数据库、消息队列)如何协同参与一个全局事务,并保证 ACID 特性的理论模型。
其本质就是一种全局事务模型,可以把它理解为分布式事务的“顶层设计”或“蓝图”。它不关心具体如何实现,而是定义了有哪些角色以及角色之间如何交互。
推荐阅读:《Distributed Transaction Processing: The XA Specification》
应用程序 (Application Program, AP): 一个应用程序就是一个实例,负责发起事务
资源管理器 (Resource Manager, RM): 主要管理自身资源,执行实际的事务操作(如修改数据),并处理与事务相关的指令(如准备、提交、回滚)。一个全局事务通常涉及多个RM。
事务管理器 (Transaction Manager, TM): 分布式事务的大脑和总协调者。它是一个独立的组件。
通信资源管理器 (Communication Resource Manager, CRM): 管理分布式系统中不同节点(AP、TM、RM可能不在同一台机器上)之间的通信。协调不同节点/进程间的交互,确保消息能够正确传递。通常由底层中间件(如TPMonicitor)实现,对开发者透明。
通信协议 (Communication Protocol, CP): 定义了TM、RM、AP之间相互通信的接口和规则。最重要的协议:就是 XA 协议,它定义了 TM 和 RM 之间的交互接口。
在 DTP 模型中,AP 向 TM 注册一个全局事务,然后通过 RM 的接口访问资源。TM 通过 XA 接口(属于CP)来协调和管理所有注册到这个全局事务中的 RMs。CRM 为整个交互过程提供通信支持。
一个全局事务的成功与否,取决于一个个 RM 资源管理器的内部工作的操作是否成功,还取决于其他资源管理器的操作。如果任何操作在任何地方失败,那么每个参与的 RM 资源管理器必须回滚它为全局事务做的所有操作。
一个资源管理器通常不知道其他资源管理器做了什么工作。TM 事务管理器告诉每个资源管理器全局事务的存在,并协调它们完成这些事务。一个资源管理器负责把它的可恢复的工作单元映射到全局事务。
理解其工作原理
用一个电商场景中的下单做为场景,一个下单业务中可能要求更新几个不同的数据库,应用系统中任何地方发生的工作都必须原子提交,同时每个 RM 都必须让 TM 协调全局事务中包含的 RMs 的可恢复工作单元,保证失败后的回滚操作。
XA 规范是 DTP 模型的核心实现方式。 它是由X/Open定义的 TM 与 RM 之间的接口规范。一套遵循 XA 规范的 API,通常以 xa_ 开头,例如:
其工作流程如下:
AP(你的程序)通过TM(如一个Java事务管理器)声明一个全局事务。
AP 通过数据库驱动(作为 RM 的代理)执行 SQL。执行前,驱动会调用 xa_start
将当前连接与全局事务XID 绑定。
AP 结束时,通知 TM,而 TM 开始执行 2PC 协议:
第一阶段:TM 调用所有参与 RM 的 xa_prepare
方法。各 RM 将事务内容持久化到磁盘(Redo Log),并锁定资源,然后返回是否成功。
第二阶段:如果所有 RM 都 prepare 成功,TM调 用所有 RM 的 xa_commit
方法提交;如果任何一个失败,TM 调用所有 RM 的 xa_rollback 方法回滚。
2PC、3PC 协议
2PC/3PC 其实是 DTP 里 TM 控制提交的具体算法实现 ,掌握上面 DTP 模型后,在看这句话就能把 DTP 和 2PC 串起来了。3PC 作为 2PC 的优化版本,也得点出改进点和代价。
2PC(两阶段提交)是 DTP 中最常用的协议,分两步,就像组织聚会:
先问"下周六能来吗?",如果都答应,就发邀请函;如果有人不能来,就取消聚会。
3PC(三阶段提交)是 2PC 的改进版,增加了"预准备"阶段,避免了 2PC 中"等待"的尴尬情况。 就像组织聚会时,先问"下周六有空吗?",如果都回答"有空",再问"确定能来吗?",最后才发邀请。
强一致性解决方案保证了数据的绝对一致,但代价是系统可能在等待过程中变得"慢"或"不可用",就像聚会组织者要等所有人确认后才能发邀请,如果有人迟迟不回复,聚会就一直等。
主流数据库(MySQL InnoDB, Oracle, PostgreSQL等)都提供了对 XA 规范的支持。
在 Java 中,JTA (Java Transaction API) 是 XA 规范的Java版实现,而像 Atomikos、Narayana这样的库就是实现了 JTA 的TM,还有 Dromara 开源社区的 RainCat 框架也在应用层支持 XA 规范。
通过本文对 DTP 的讲解,能认识到 强一致性解决方案的实现方式与执行流程,其中了解到 DTP 模型
、XA 规范
、2PC 协议
、3PC 协议
这是一个概念层级的问题。
在 DTP 模型中,事务管理器 (TM) 使用 2PC(或3PC)这个协议作为其协调资源管理器 (RMs)的核心机制,实现强一致性解决方案的。
整体来看,强一致性分布式事务解决方案要求参与事务的各个节点的数据时刻保持一致,查询任意节点的数据都能得到最新的数据结果。这就导致在分布式场景,尤其是高并发场景下,系统的性能受到影响
最终一致性分布式事务解决方案是基于 AP + BASE 理论 为基础,并不要求参与事务的各节点数据时刻保持一致,允许其存在中间状态,只要一段时间后,能够达到数据的最终一致状态即可,这也是互联网公司最常用的模式。
在实现最终一致性时,有几个通用的保护措施是一定要考虑的:
唯一性标识的可查询操作: 给每一笔分布式事务发一个唯一的“身份证号”(全局事务ID),凭这个号可以查到事务到底进行到哪一步了,是成功还是失败。
同一业务处理的幂等操作: 因为网络可能重试,同一个请求可能会来很多次。所以要保证无论来多少次,最终的结果都和只来一次一样。比如扣款操作,即使因为超时重试了,也只能扣一次钱。
事务调用异常的监控与告警操作: 要有完善的监控系统,一旦发现某个事务卡住了或者失败了,要能立刻报警通知程序员小哥来“救火”。
针对本次业务事务数据的补偿操作: 做错了就要认,挨打了要立正。如果事务失败了,一定要有一个反向操作来“擦屁股”,比如刚才扣了钱,现在就要把的钱加回去,这叫补偿。
目前主流的最终一致性解决方案实现方式有以下三种,此处都是简单过下,在思考最终一致性时能立马构思出其解决方案,后续会有专门的章节讲解源码级的实现,在想到其方案能立马构思出其源码实现:
TCC就是一个典型的“补偿型”选手,它把一件事分成三个步骤:
Try (尝试): 预留资源。
比如下单减库存场景,假设有100件可售库存,当下单后是先锁定10件库存,后续下单需要从100件中减去10件锁定库存,还剩下90件可售库存,此时订单状态为待确认。
Confirm (确认): 确认操作。
如果所有参与方的 Try 预留资源操作都成功了,就执行真正的确认操作。比如把冻结的10件库存真正扣掉,那可售库存就是90件,此时订单状态改为已确认。
Cancel (取消): 补偿操作。
如果 Try 阶段有任何一个失败,就执行 Cancel,释放之前冻结的所有资源。比如把冻结的10件库存释放回可售库存里。
整体来看,TCC 对业务代码侵入性强,需要你专门写 Try、Confirm、Cancel三个接口。它的好处是性能好,数据最终一致性强。坏处是开发工作量比较大。
这个方案的核心是找一个无比靠谱的“邮差”(消息队列) 来帮忙传递事务消息。
可靠消息方案需要解决两个问题:
这种方式依靠消息队列的可靠性,保证了只要消息发出去了,最终就一定能被处理。它非常适合异步场景,比如下单后发优惠券、通知物流系统等。它的缺点是整个流程比较长,时效性没那么高,如果超过重试次数还未执行成功,需要人工干预。
这是要求最低的一种最终一致性方案,常见于外部系统调用,比如通知支付结果。
服务A(比如支付系统)完成任务后,会一次又一次地(最大努力)调用服务B(比如订单系统)提供的接口,告诉它处理结果。
服务B收到通知后,处理完返回一个成功响应。
如果服务A一直没收到成功响应,就会按照策略反复重试(比如隔1s、5s、10s...重试),直到达到重试次数上限为止。
最大努力通知这个方案的目标是“通知到”,但不保证100%成功。下游服务的接口必须做好幂等。即使通知失败了,也需要有对账机制来最终修正数据。这是一种业务容忍度最高、实现也相对简单的方案,但可靠性稍低。适合对一致性要求不那么高的场景,比如通知类业务。
最大努力通知的关键是"不断重试"和"人工兜底"。虽然不能保证100%成功,但通过不断重试和人工干预,可以达到较高的成功率。
分布式事务的思维模型其实很简单:要么追求强一致(CP),要么追求最终一致(AP)。
理论基石:
分布式系统只能在CP(强一致)和AP(最终一致)中做选择(CAP理论),而 BASE 理论是 AP 方向的最佳实践指南。
两大阵营:
强一致性(CP):强一致性解决方案(DTP、2PC、3PC)保证了数据绝对一致,但代价是系统可能变得"慢"或"不可用",适合金融交易等对一致性要求高的场景。
最终一致性(AP):是互联网主流选择,牺牲了实时一致性,但换来更高的可用性和更好的性能,适合电商、社交等对一致性要求不那么高的场景。旗下有三大将,对于选择哪种方案,要看你的业务需求:
有了这个思维模型,你就能理解各种分布式事务框架(如Seata、RocketMQ事务消息)的设计初衷和适用场景了。 在接下来的文章中,我们将深入这些方案的实现原理,看看主流框架是如何玩转这些模型的,以及在实际业务中我们该如何根据场景进行选型。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!