开篇:
搞分布式事务,就像在管理一个团队。
- 2PC是事无巨细、全程紧盯的微观管理者,累但严谨;
- TCC是把任务拆解、责任到人,但流程复杂的中层管理者;
- 基于消息的模式,则是定好目标、充分放权、只关注最终结果的领导者。
在Spring Boot这个便利的框架下,工具都给你备好了,但用哪个、怎么用,考验的是你对业务和架构本质的理解深度。
接下来呢,我们将针对以上提到的内容具体分析下。
一、核心矛盾:从“自家事”到“别人家的事”
想象一下,你是个单体应用的“城主”。城里所有事(数据库操作)都在你的城堡(单个数据库)里完成。你要么全成功(Commit),要么全回滚(Rollback),这就是本地事务,靠数据库的ACID特性就能搞定,简单粗暴。
但现在,业务扩张,你成了“联邦制”的“总统”。订单服务、库存服务、用户服务各自为政,有自己独立的数据库(资源)。这时候,你想完成一个“创建订单->扣减库存->增加积分”的跨服务操作,问题就来了:
你无法再用一个数据库事务来统一管理这三个独立数据库的操作了。
这就是分布式事务要解决的核心矛盾:如何在分布式系统中,保证一系列跨网络、跨资源的数据操作的业务一致性。
请注意,我这里强调的是业务一致性,而不是传统意义上严格的ACID。这是我们首先要转变的观念。
二、主流方案的精髓与“脾气秉性”
在Spring Boot生态里,我们打交道的主要是以下几种方案,每一种都有它的“适用场景”和“脾气”。
1. 两阶段提交(2PC):中央集权的老将军
-
现实编码场景:我们代码中经常会用到
@Transactional注解,但背后其实是一个JTA事务管理器在协调多个XA兼容的数据源。 -
工作模型:
- 阶段一(准备) :事务协调者(Transaction Coordinator)问所有参与者(订单DB、库存DB):“我要干个事,你们准备好了吗?能成功吗?” 参与者们锁定资源,记录日志,然后回答:“Ready!”
- 阶段二(提交/回滚) :如果所有人都说Ready,协调者就发令:“Commit!” 如果有人说不,或者超时,就发令:“Rollback!”
-
理想与现实:
-
优点:强一致性。在理想情况下,它能保证所有节点要么都成功,要么都失败。
-
残酷的现实(为什么现在用得少) :
- 性能瓶颈:同步阻塞。在准备阶段,所有参与者的资源(如数据库行锁)都处于锁定状态,要等到第二阶段才释放。在高并发下,这是致命的。
- 协调者单点故障:如果协调者挂了,整个系统就“群龙无首”,参与者会一直持有锁,导致系统不可用。
- 数据不一致的幽灵依然存在:如果第二阶段,协调者只让部分参与者提交成功,然后自己崩溃了,就会导致数据不一致。这是个经典问题。
-
我的看法:2PC像一位作风老派、流程繁琐的老将军。适合内部系统、流程不长、并发不高,且对一致性要求极高的场景(比如银行内部的某些传统系统)。在微服务架构下,它通常不是首选,因为它的“协调成本”太高,不符合微服务追求松耦合、高可用的理念。
-
2. TCC模式:靠“代码逻辑”兜底的实干家
-
现实编码场景:你不会再用一个全局的
@Transactional了。你需要为每个服务(如库存服务)编写三个业务方法:try():尝试执行。比如,检查库存是否充足,然后预扣减库存(将库存从一个状态变为另一个中间状态,例如status = ‘FROZEN’)。confirm():确认执行。真正扣减预占的库存,status = ‘DEDUCTED’。cancel():取消执行。释放预占的库存,status = ‘AVAILABLE’。
-
工作模型:事务管理器依次调用所有服务的
try(),如果全部成功,则再依次调用confirm()。如果任何一个try()失败,则依次调用已成功服务的cancel()进行回滚。 -
理想与现实:
-
优点:最终一致性、性能较好(资源锁定时间短,只在Try阶段)。
-
残酷的现实:
- 代码侵入性极强:你需要改造每一个业务逻辑,设计出T、C、C三个阶段的实现。这对业务代码是种“污染”。
- 设计复杂度高:如何设计“预占”状态?如何保证幂等性(防止网络重试导致Confirm/Cancel被重复调用)?如何防悬挂(Try超时后Cancel了,但后续Try请求又来了)?这些问题都需要开发者手动处理,心智负担很重。
- 业务场景限制:不是所有业务都能轻松拆出TCC三步骤。比如,通知短信发送,你怎么“Cancel”?难道发一条“不好意思,刚才那条不算”?
-
我的看法:TCC是“用开发的复杂性换取架构的灵活性”。它非常适合对一致性要求高、性能敏感、且业务模型本身可以自然地分解出“预留资源”概念的场景,比如金融、交易核心链路。选择TCC,意味着你的团队要有足够的设计和运维能力来驾驭它。
-
3. 基于消息的最终一致性:微服务世界的“和事佬”
这是目前最流行、最符合微服务哲学的模式。它的核心思想是:靠消息队列的可靠性,来驱动后续操作的执行,从而达到最终一致。
-
现实编码场景:以“下单扣库存”为例。
-
方案A(本地消息表) :你的订单服务在本地数据库事务中,除了创建订单,还同时往一张“本地事件表”插入一条“库存扣减”事件。然后有一个定时任务扫描这张表,将事件发给MQ。库存服务消费消息并扣减库存。成功后,删除或标记该事件。
-
方案B(RocketMQ事务消息) :这是更优雅的方式。
- 订单服务发送一个
半消息到MQ(此消息对消费者不可见)。 - MQ回复“半消息发送成功”。
- 订单服务执行本地事务(创建订单)。
- 根据本地事务执行结果,向MQ发送
Commit或Rollback指令。 - 如果MQ收到Commit,则消息变为对消费者可见,库存服务开始消费。如果一直是
未知状态,MQ会回调订单服务的一个接口来查询事务状态。
- 订单服务发送一个
-
-
理想与现实:
-
优点:
- 吞吐量高:异步化,解耦彻底。
- 最终一致性保证好:依靠MQ的高可用和持久化。
- 对业务侵入相对较小:尤其是在事务消息方案中,你主要关注本地事务和状态查询接口。
-
残酷的现实:
- 延迟性:数据不是实时一致的,会有秒级甚至分钟级的延迟。
- 消费者幂等性必须保证:因为网络问题可能导致消息重投,你的库存服务接口必须能够正确处理“同一条扣库存消息被消费多次”的情况(例如,通过订单ID+业务状态判断)。
- 消息链路追踪复杂:出了问题,日志散落在多个服务和一个MQ中,排查需要一套好的链路追踪系统。
-
我的看法:这是绝大多数业务场景的“银弹” 。它平衡了一致性、性能和复杂度。它承认了分布式系统的不完美,接受了“最终一致”的现实,转而追求系统的整体弹性和高可用。在做技术选型时,除非有强一致性要求,否则应优先考虑此方案。
-
三、通过以上的分析,我们可以从中分析得出以下结论(个人见解,不能一概而论):
-
能不用分布式事务就不用:这是最高指导原则。重新设计业务,避免跨服务的事务。比如,能不能通过“合并服务”、“字段冗余”、“最终核对与补偿”等方式来规避?
-
从“强一致”的思维定势中走出来:接受最终一致性。问问产品和业务,我们这个场景,数据延迟几秒钟,能接受吗?99%的场景都是可以的。
-
技术选型看场景:
- 内部、小集群、强一致 -> 考虑 2PC。
- 金钱、核心交易、能忍受复杂编码 -> 考虑 TCC。
- 互联网业务、高并发、追求可用性 -> 基于消息的最终一致性 是你的首选。
-
别忘了幂等、补偿和可观测性:
- 幂等性:在分布式世界里,任何操作都可能被重试,你的接口必须是幂等的。
- 补偿机制:TCC的Cancel、Saga模式的补偿事务,都要想好。做不成,怎么优雅地回退?
- 可观测性:强大的日志、监控和链路追踪,是你排查分布式事务问题的“眼睛”。没有这个,线上问题能让你彻夜难眠。
517

被折叠的 条评论
为什么被折叠?



