为什么分布式事务如此困难
分布式事务的核心挑战在于CAP定理的固有限制。在分区容忍性(P)必须保证的前提下,我们只能在一致性(C)和可用性(A)之间做出取舍。随着微服务架构的普及,单一业务操作往往需要跨越多个服务、多个数据库,传统的ACID事务模型在分布式环境下无法直接应用。
具体而言,分布式事务面临以下核心难题:
- 网络不确定性:消息丢失、延迟、重复、乱序等问题在分布式环境中无法避免
- 节点故障:任何参与者、协调者随时可能宕机或无法响应
- 性能瓶颈:两阶段提交的同步阻塞特性在高并发场景会成为严重的性能瓶颈
- 时钟漂移:不同节点的物理时钟存在偏差,影响基于时间戳的排序和可见性判断
- 隔离级别不足:大多数分布式方案只能提供读已提交或更弱的隔离级别
经典方案:2PC与3PC
两阶段提交(2PC, Two-Phase Commit)是分布式事务的经典协议,由IBM在1970年代提出。它引入一个协调者(Coordinator)和多个参与者(Participants),通过两个阶段的协议保证所有节点要么全部提交、要么全部回滚。
阶段一:投票阶段(Voting Phase):协调者向所有参与者发送PREPARE请求,参与者执行事务操作(写入undo/redo日志),但不提交。参与者将执行结果(YES或NO)返回给协调者。
阶段二:提交阶段(Commit Phase):如果所有参与者都返回YES,协调者发送COMMIT指令,参与者正式提交事务;如果有任何一方返回NO或超时,协调者发送ROLLBACK指令,参与者回滚事务。
2PC的最大问题是同步阻塞和单点故障:
- 所有参与者在投票阶段会持有锁等待协调者决策,高并发场景下数据库连接和锁资源快速耗尽
- 协调者宕机时,参与者无法获取最终决策,事务将无限期阻塞(需要人工介入或超时机制打破)
- 网络分区导致协调者与参与者失联时,已进入提交阶段的事务可能产生脑裂(split-brain)
三阶段提交(3PC, Three-Phase Commit)通过引入PRE-COMMIT阶段和超时机制来缓解2PC的阻塞问题:
阶段一CAN_COMMIT:协调者询问参与者是否能执行业务(轻量级检查,不加锁)
阶段二PRE_COMMIT:所有参与者同意后,协调者发送预提交指令,参与者执行操作但不提交
阶段三DO_COMMIT:所有预提交成功后,协调者发送最终提交指令
3PC通过当协调者故障时,参与者在超时后默认提交(第一阶段已确认参与者状态)来避免无限阻塞,但数据不一致的风险在网络分区时依然存在。
TCC(Try-Confirm-Cancel)灵活事务
TCC是一种业务层面的分布式事务协议,通过将业务操作拆分为三个阶段来实现最终一致性:
Try阶段:预留资源并完成业务规则校验(如检查账户余额是否充足、库存是否足够、是否超卖等),但不实际扣减。例如电商下单场景,try操作可能是冻结资金、预占库存、生成订单(状态为"待支付")。
Confirm阶段:在try成功后调用,正式完成业务操作。Confirm操作应该是幂等的,能够安全重试。例如扣减冻结资金、减少预占库存数量、将订单状态改为"已确认"。
Cancel阶段:当Confirm失败或业务异常时调用,回滚try阶段预留的资源。Cancel同样需要幂等性,能安全重试。例如释放冻结资金、归还预占库存、将订单状态改为"已取消"。
TCC的优势在于完全不依赖数据库事务,而是通过业务逻辑实现分布式协作。但这意味着侵入性强——每个参与服务都需要实现try、confirm、cancel三个接口,业务复杂度显著增加。
主流的TCC框架包括Seata(阿里开源)、ByteTCC、EasyTransaction等。Seata的AT模式可以零侵入地使用数据库的undo log实现两阶段提交,是TCC的简化替代方案。
Saga模式:长事务的最终一致性
Saga是由Garcia-Molina在1987年提出的长事务管理模型,最初用于银行数据库的批处理场景。在微服务架构中,Saga被重新定义为协调多个服务间一系列本地事务的模式。
核心理念:一个长时间运行的分布式事务(如跨5个服务的订单创建流程),被拆分为一系列按顺序执行的事务操作(T1, T2, T3, T4, T5),每个操作对应一个补偿操作(C5, C4, C3, C2, C1)。如果第3步执行失败,则按逆序执行补偿操作(C2, C1)来回滚之前已完成的步骤。
编排模式(Choreography):每个服务发布事件,其他服务监听事件并执行下一个步骤。没有中央协调者,依赖事件驱动的消息传递。优点是实现简单,缺点是流程难以理解和调试(需要追踪事件流才能知道剩余步骤)。
编排模式(Orchestration):引入中心化的Saga编排器,由它负责调用各个服务、监听响应、推进流程或触发补偿。流程清晰可控,适合复杂业务场景,缺点是引入了单点(编排器本身)。
常见的Saga实现框架:
- Eventuate Tram Sagas(Java生态,支持Choreography和Orchestration两种模式)
- Seata Saga(支持状态机引擎定义流程)
- Camunda / Flowable(BPMN工作流引擎,支持Saga的编排模式)
- MicroProfile LRA(Jakarta EE标准,基于REST的Saga实现)
Saga的挑战:
- 隔离性缺失:Saga的每个本地事务立即提交,中间状态对其他事务可见,可能导致脏读
- 补偿复杂性:退款、退货等业务操作的补偿可能是不可逆的(如已发货的商品只能通过物流追踪)
- 幂等性保证:报文和重试机制要求每个步骤和补偿操作必须是幂等的
消息驱动的事务方案:本地消息表与事务消息
本地消息表(Local Transaction Table)是一种朴素的可靠消息最终一致性方案。核心思想是将业务操作和消息写入放在同一个本地事务中(在业务库中增加一个transaction_message表),然后由额外的投递服务异步推送消息到MQ。
流程:1)写入业务数据和消息记录(同一事务)→ 2)定时任务或独立服务扫描未投递的消息 → 3)投递到MQ → 4)消费方处理业务并ACK(或重试/报错)。
事务消息(如RocketMQ的Half Message)是消息中间件内置的事务支持。核心流程:
1)发送Half消息(队列不可见,消费者无法消费)→ 2)执行本地事务 → 3)提交Local Transaction结果(COMMIT/ROLLBACK)→ 4)COMMIT后消息对消费者可见,ROLLBACK后消息删除
如果本地事务结果未定,Broker会通过回溯机制检查本地事务状态(调用producer的回查接口),实现最终一致性。
NewSQL时代的分布式事务
NewSQL数据库通过重新设计架构,在分布式环境下实现了跨越多个节点的ACID事务:
Google Spanner:基于TrueTime API(GPS + 原子钟)实现全球分布的外部一致性(External Consistency),是分布式事务的理论到工程的巅峰之作。它通过Commit Wait机制保证线性一致性,是高隔离性的代价是写入延迟(10-100ms全球部署)。
TiDB:基于Percolator事务模型(Google早期论文实现),支持乐观锁和悲观锁两种模式。通过PD(Placement Driver)调度多副本(Raft协议),提供Snapshot隔离级别。
CockroachDB:完全兼容PostgreSQL协议的多活数据库,使用并行提交(Parallel Commits)优化两阶段提交。通过SERIALIZABLE隔离级别和可串行化快照解决写倾斜问题。
YugaByte DB:基于DocDB的分布式文档存储之上构建SQL层,支持Redis和Cassandra两种API。通过DocDB的事务管理器实现跨行ACID,延迟比Spanner低(通常10ms以内单REGION)。
方案选型与工程权衡
没有银弹般的分布式事务方案,每种方案都有其适用场景和代价。选型时应该综合考虑业务特性、性能要求、团队技术栈和运维成本:
2PC:适合强一致性要求高的传统系统(如金融核心交易),参与者少(2-3个)、事务短(秒级)、并发低(TPS小于1000)。注意处理好协调者单点故障问题。
TCC:适合跨微服务的准实时业务(如电商支付、充值),需要业务方主动配合实现三接口。适合对隔离性有一定要求但不需要串行化的场景。
Saga:适合长流程的业务编排(如旅游预订:机票+酒店+租车+保险),可接受数据最终一致,补偿逻辑可业务定义。
本地消息表 / 事务消息:适合通知型业务(如短信推送、邮件发送、积分变动),对实时性要求不高,最终一致即可。
NewSQL:适合大型OLTP系统,无法接受数据不一致,且有能力承担数据库迁移成本。强烈建议新项目直接采用NewSQL方案。
总结
分布式事务是分布式系统中最复杂的设计之一。从2PC的强一致性,到TCC的业务级原子性,到Saga的最终一致性,再到NewSQL的工程化极致,不同方案恰如光谱上的不同频段。关键是理解每种方案背后的一致性假设和性能边界,根据业务的真实需求做出取舍。
值得欣慰的是,随着分布式共识算法(Raft/Paxos)、云原生数据库(TiDB/CockrogressDB)和事件驱动架构(CQRS/Event Sourcing)的成熟,我们正处在一个分布式事务越来越可控的时代——只要付出合理的成本,选择正确的方案,分布式事务的建设难度正降到可管理的范围内。

发表评论 取消回复