[Java微服务架构]7-3_事务处理——分布式事务
分布式事务
- 5.分布式事务(Distributed Transaction)
- CAP理论与ACID的矛盾(难以达成的强一致性)
- 最终一致性
- BASE模型
- BASE模型
- BASE模型实现方式
- 可靠事件队列
- TCC模式
- Sage模式
- 事务拆分与补偿策设计
- 实现方式
- 编排式 Saga(Choreography)
- 指挥式 Saga(Orchestration)
- Sage总结
5.分布式事务(Distributed Transaction)
分布式事务(Distributed Transaction)特指多个服务同时访问多个数据源的事务处理机制。
CAP理论与ACID的矛盾(难以达成的强一致性)
CAP理论描述了在一个分布式系统中,涉及共享数据问题时, CAP三个特性只能满足其中两个。
在事务这个篇章,肯定是需要满足一致性 Consistency的,一致性按照过程又分为强一致性与弱一致性。
- 强一致性
强一致性要求分布式系统中所有节点在任何时刻看到的数据都是完全一致的。即,一旦某个数据被更新,所有后续的读取操作(无论从哪个节点读取)都能立即反映最新的值。 - 弱一致性(最终一致性)
允许数据源集群节点的数据状态有短暂的不一致。在更新后的短暂时间内,不同节点可能看到的数据不一致,但随着时间推移(一些同步机制),数据会收敛到一致。
了解定义后,我们很容易明白前面同样是多数据源的“全局事务”追求的是强一致性,ACID的C本身追求的也是强一致性。
然而,在分布式多服务多数据源的情况下,追求强一致性会有一些根本性困难。
- 1.网络不可靠
在全局事务中我们有了解到,无论是2PC还是3PC,都无法解决网络不可靠的问题,只能说3PC多了一轮询问,事务成功的可能性更大,且解决了超时问题,但是一致性在不可靠网络情况下还是很难保证。
在分布式多服务系统中,网络会更不可靠,追求强一致性C就会牺牲可用性A。 - 2.分布式系统故障是常态
多服务多数据源意味着很多的参与方,任何一个节点故障都可能导致事务失败。
故障重试、超时等待都会有性能损耗,违背了分布式追求高新能的目标。而且事务在分布式系统中涉及资源范围大,故障时因追求强一致性使用的锁持有时间也会增加,又违背了分布式追求高并发的目标。
其他困难还有协调成本指数增长导致的可扩展性受限等……
全局事务追求强一致性(C),但在多服务、多数据源的分布式系统中,P 是常态,因此强一致性只能通过牺牲 A(可用性)实现。而许多业务场景(如电商、社交网络)无法接受服务不可用,这些场景多选择最终一致性。
最终一致性
最终强一致性难以实现就实现最终一致性嘛,不过继续了解最终一致性之前,我们需要先留了解BASE模型,BASE模型系统化描述了如何在分布式系统中实现最终一致性。
BASE模型
BASE模型是作为ACID模型的对立面或补充,提供了一种在分布式系统中实现一致性的替代思路。
“BASE”是基本可用性(Basically Available)、柔性事务(Soft State)和最终一致性(Eventually Consistent)的缩写。
BASE模型是对最终一致性思想的系统化总结和应用,其核心思想是“放弃实时强一致性,转而追求高可用性和系统的柔韧性,最终通过时间和补偿机制达到一致状态”。
模型的概念与理解如下⬇️:
BASE模型
-
Basically Available(基本可用性)
- 含义:系统大部分时间保持可用。即使出现故障或也能尽量提供服务,而不是完全不可用。
- 实现:熔断+服务降级或流量控制,或者牺牲部分一致性以保证响应,比如服务降级中使用旧数据缓存。
- 示例:电商系统在高峰期可能暂时显示“库存稍后更新”,但仍接受订单,而不是拒绝服务。
具体的可用性可以看“异常处理”这篇,都是保证可用性,基本可用性实现基本是柔和的服务降级。
-
Soft State(柔性状态)
- 含义:系统状态可以在一段时间内处于不一致或“柔性”的状态,不要求所有节点实时同步。状态的变化是渐进的,可能需要外部干预(如同步机制)来调整。
- 实现:数据副本间允许短暂差异,依赖异步更新或补偿机制逐步修正。
- 示例:社交媒体点赞数在不同服务器上可能短时间内不同,用户看到的数据是“软状态”,最终会同步。
这部分主要是理解柔性的概念,即不一致也没关系,主要是在设计系统、分析业务的时候,要能抓住应用场景中的柔性的部分,当然,不要忘记目标,即设计补偿机制达成最终一致。
总结:理解柔性并针对柔性点设计+补偿机制以最终一致。 -
Eventually Consistent(最终一致性)
- 含义:如果没有新的更新操作,系统会在一段时间后达到一致状态,所有节点最终看到相同的数据。
- 实现:通过后台同步(如日志重放、消息队列)或冲突解决(如版本号、时间戳)实现一致性。
- 示例:DNS 更新后,全球服务器逐步同步,最终所有解析结果一致。
主要是了解最终一致性的实现方式,即“一段时间后达成一致”这个一段时间做了什么,如下⬇️
BASE模型实现方式
-
异步复制:
- 主节点完成写操作后立即返回,异步将更新传播到其他节点。
- 示例:Cassandra 的写操作,客户端写入成功后,副本逐步同步。
这里的传播方式有“状态转移”与“操作转移”,具体可以看这篇"分布式数据共享方式"。
即:异步传播+等待 -
补偿机制(Saga 模式):
- 将大事务拆分为多个小事务,每个小事务直接提交。若某个小事务失败,则通过执行补偿操作回滚所有已提交的小事务,以实现最终一致性。
- 示例:订单处理中,扣款成功但发货失败,则执行退款补偿,恢复初始状态。这种方式不同于传统事务的同步回滚(如 2PC),而是基于应用层的异步补偿。
即分阶段完成事务,不用同2PC或者3PC一样整体事务都完成才算,而是可以分阶段完成,且不同阶段都有阶段状态感知,感知后进行失败补偿方案。
需要应用场景设计耦合低才方便做拆分与补偿。 -
事件驱动:
- 通过消息队列(如 Kafka)发布更新事件,其他节点订阅并应用变更。
- 示例:库存减少后发布事件,通知其他服务更新缓存。
数据异步通信传播的一种方式,但更适合分布式系统多服务多通信,因为有队列可以缓冲请求数据,且可以重试等。
-
冲突解决:
- 使用版本号、向量时钟或业务规则(如“最后写入胜出”)处理数据冲突。
- 示例:DynamoDB 使用版本号确保最终一致。
最终要达成的一致性,可以通过版本号来辨别是否完成操作。
可靠事件队列
可靠事件队列是以消息队列为中间件,将分布式事务投递进队列,利用消息队列的确保消费的机制进行进行事务提交。体现了BASE模型的“事件驱动”,同时,也体现了Sage模型的事务拆分+补偿。
设计上来说,也是很好的异步解耦防阻塞的事务方式,时序图如下⬇️:
核心是“事件队列”+确认机制(Ack Mechanism),即常见的消息队列,消息队列确保消费后才移除的特性。
TCC模式
在共享事务一节中,我们有了解到共享事务多服务单数据源存在“事务隔离级别不足,需要服务应用协调控制来保证数据的一致性”问题。
分布式事务也有这样的隔离级别不足问题,在了解过BASE模型的实现后,很显然,其并没有处理这个问题,还是需要服务引用协调来控制,即需要应用层模拟隔离性。
TCC分布式事务机制就是为了解决分布式事务“隔离级别不足问题”而诞生的。
TCC是另一种常见的分布式事务机制,它是“Try-Confirm/Cancel”3个单词的缩写。
如名字所示,分为3个阶段⬇️
-
Try:尝试执行阶段,完成所有业务可执行性的检查,并且预留好全部需要用到的业务资源(保障隔离性)。Try阶段可能会重复执行,因此本阶段执行的操作需要具备幂等性。
即检查并预留资源,预留即用锁的方式提前锁定资源。
例如:冻结库存、预扣账户余额 -
Confirm:确认执行阶段,不进行任何业务检查,直接使用Try阶段准备的资源来完成业务处理。Confirm阶段也可能会重复执行,也需要具备幂等性。
即如果Try阶段成功,则执行真正的提交操作,完成资源变更。
例如:确认库存减少、确认扣款 -
Cancel:取消执行阶段,释放Try阶段预留的业务资源。Cancel阶段也可能会重复执行,也需要具备幂等性。
即Try失败,进行回滚,释放预留资源。
例如:解冻库存、退回余额。
以转账场景为例
总结一下TCC Try+Confirm/Cancel就是通过预留资源和补偿机制避免分布式事务的阻塞问题(较2PC与3PC)。不依赖数据库的隔离性,将隔离性实现提前到应用层,解决分布式事务隔离级别不够问题。
这样前面全局事务的阻塞问题+共享事务隔离级别不够问题就都解决啦。
还有网络问题,TCC的3个阶段都要求支持幂等=支持重试,可以一定程度上解决网络不可靠问题。
TCC总的来说,比2PC灵活且不用等待,是非阻塞状态。
且提供了分布式事务的“网络不可靠”、“隔离级别需要服务应用协调”、“协调者需要阻塞等待参与者结果”这3个问题的解决方案,性能更高,也和BASE模型一样体现了事务拆分,更灵活。
缺点是都需要为事务中的每个服务实现Try、Confirm、Cancel 这3个接口,且接口要求幂等+重试逻辑,设计和测试难度较高,逻辑更复杂,因为一般分布式事务方案都是融合的,比如TCC+可靠时间队列(异步处理)+补偿机制(其他操作)+基本可用(服务降级)。
Sage模式
TCC模式很好,能解决多数问题。但是其在部分场景应用有一个缺陷,即在TCC的Try阶段,核心的机制“资源预留”——预留不了怎么办?例如:银行业一些数据资源。
此时,TCC适用性收到限制,有没有一个事务机制或模式可以和TCC一样解决分布式事务的大多数问题呢?
有的,那就是Sage模式。
Saga 是一种分布式事务模式,用于处理长生命周期事务(Long-Lived Transactions)。在分布式系统中,Saga 将一个全局事务拆分为一系列本地事务,每个本地事务由独立的服务执行,并通过补偿机制(Compensation)保证最终一致性。
在BASE模型中有提过Sage模式,这里算是战术展开仔细了解。
Sage核心思想就是 事务拆分+顺序执行+补偿机制。
-
事务拆分:将一个大事务分解为多个小的本地事务,每个事务直接提交(无预留阶段)。
Sage拆分后的事务和2PC还有TCC都不一样,不阻塞(都拆了等啥)也不预留资源,而是直接提交。
大事务拆分成小事务后可以不会长时间锁定数据库的资源。 -
顺序执行:本地事务按顺序(或并行)执行,前一个事务成功后触发下一个。
这么做的理由是方便失败时进行补偿。事务顺序执行,补偿与事务意义对应顺序恢复。 -
补偿机制:如果某个本地事务失败,已成功提交的本地事务会通过对应的补偿操作抵消其影响,补偿的触发可以由中央协调者指挥(指挥式Sage)或通过事件分布式协作(编排式Sage)完成。
这里补偿动作的进行与Sage模型的不同实现方式有关,补偿动作的本质是反向操作抵消影响。
事务拆分与补偿策设计
Sage事务拆分方式如下⬇️:
将大事务拆分成若干个小事务,将整个分布式事务 T 分解为 n 个子事务,命名为 T1, T2, …, Ti, …, Tn。每个子事务都应该是或能被视为原子行为。如果分布式事务能够正常提交,其对数据的影响(即最终一致性)应与按顺序连续成功提交所有子事务 T1, T2, …, Tn 的效果等价。
为每一个子事务设计对应的补偿动作,命名为C1,C2,…,Ci,…,Cn。
Ti与Ci必须具备幂等性、满足交换律(无论先执行T1还是C1,效果是一样的),补偿动作C不许能提交成功,即补偿必须成功。
在拆分事务和建立事务补偿机制时,必须确保每个子事务与其对应的补偿操作之间存在明确的联系,以保证失败时能够正确抵消已提交事务的影响。
Saga 模式中失败补偿机制的本质是通过执行反向操作回滚已完成的事务,但其具体实现可能因场景而异,显得有些灵活。
例如,可以结合失败重试(尝试执行补偿操作至最大重试次数)确保补偿成功,或在特定情况下通过服务降级作为辅助手段以维持系统可用性,但这些并非补偿机制的定义本身。
比如,有两种事务恢复策略⬇️,其中正向恢复在定义上不算做补偿机制:
- 正向恢复(Forward Recovery):如果子事务 Ti 提交失败,则持续重试 Ti 直到成功(最大努力交付)。这种策略不涉及补偿操作,适用于事务最终必须成功的场景,例如“银行扣款后必须发货”。正向恢复的执行模式为:T1, T2, …, Ti(失败), Ti(重试), …, Ti+1, …, Tn。由于不依赖反向操作,正向恢复在定义上不属于补偿机制。
- 反向恢复(Backward Recovery):如果子事务 Ti 提交失败,则执行对应的补偿操作 Ci 来抵消之前已提交事务的影响,并依次回滚之前的子事务(C(i-1), …, C1),直至所有补偿操作成功(最大努力交付)。反向恢复的执行模式为:T1, T2, …, Ti(失败), Ci(补偿), …, C2, C1。这种策略是 Saga 模式中补偿机制的典型实现,Saga 模式通常采用反向恢复。
这里不得不提一下Sage和TCC不一样的地方。
TCC事务失败不全部回滚,那一Try阶段失败,则执行对应的Cancel即可。
而Sage补偿机制全部回归和2PC类似,全部回滚至初始状态,不同的是2PC是同步阻塞,而Sage是异步补偿,后面实现方式会将。
实现方式
Sage有两种实现方式,编排式与指挥式。机制还是事务拆分+顺序执行+补偿机制,不过事务执行方式、补偿方式不同。
编排式 Saga(Choreography)
编排式 Saga 是一种去中心化的实现方式,没有中央协调者。服务之间通过事件驱动机制协作,每个服务在完成本地事务后发布事件,触发下一个服务执行。失败时通过事件触发补偿操作。
每个服务监听和发布事件,按顺序执行本地事务。若某个服务失败,发布失败事件,之前的服务监听到后执行补偿。
优点:服务松耦合,无单点故障。(适合高并发、去中心化系统)
缺点:分布式协作复杂,状态追踪困难。
指挥式 Saga(Orchestration)
指挥式 Saga 使用一个中央协调者(Orchestrator)来指挥所有服务的执行。协调者按顺序调用每个服务的本地事务,失败时调用补偿操作。
协调者依次调用服务执行本地事务。若某个事务失败,协调者按逆序调用补偿操作。
优点:逻辑清晰,易于管理和调试。(适合业务逻辑复杂、需集中控制的场景)
缺点:协调者可能成为单点瓶颈。
Sage总结
Sage较之前的2PC与TCC,因为事务回滚机制的同步与事务调度方式不同,新能上有所差异。
2PC实现比较简单,但有着网络不可靠、事务隔离级别不足问题。
TCC解决了2PC问题且是非阻塞状态,且可以有更柔性的实现,如融合服务降级+补偿机制,但是需要服务应用“预留资源”,在部分场景中使用不了。
Sage不用预留资源,且也有补偿机制,且补偿机制较2PC全部回滚更灵活,只需要回滚已经提交的,且补偿机制可以按顺序部分回滚。