分布式事务:微服务的数据一致性之困

在单体应用中,保证数据一致性是一件很简单的事。

比如下单扣库存这个操作:创建一个订单,减去一个库存,两个操作放在同一个数据库事务里,加上一个 @Transactional 注解就搞定了。要么两个都成功,要么两个都失败,不会有中间状态。

但到了微服务架构中,情况完全变了。订单和库存现在是两个独立的服务,各自拥有自己的数据库。你没办法用一个本地事务同时覆盖两个服务。

这就引出了微服务架构中最棘手的问题之一:分布式事务。如何在保证服务独立性的前提下,让跨服务的数据操作保持一致性?


一、为什么会有分布式事务

先理解问题的根源。

在单体架构中,所有数据都在同一个数据库里,ACID 事务由数据库保证。应用程序只需要开启事务、执行操作、提交或回滚,数据库会处理好一切。

微服务架构打破了这种模式。订单服务有自己的订单库,库存服务有自己的库存库。要完成一次下单,需要依次调用两个服务。如果库存扣减成功了,但订单创建失败了,库存就莫名其妙少了。

这种情况在单体中不可能发生,但在微服务中却是常态。问题的本质是:一个业务操作跨越了多个独立的数据源,而每个数据源只能保证自己的内部一致性,无法感知其他数据源的状态


二、CAP 理论与分布式事务的难度

为什么分布式事务这么难?这背后有理论约束。

CAP 定理指出,一个分布式系统最多只能同时满足三个中的两个:一致性、可用性、分区容错性。在微服务架构中,网络分区是一定会发生的,所以必须在 C 和 A 之间做取舍。

强一致性意味着所有节点在同一时刻看到相同的数据。要实现这一点,系统需要在各种异常情况下协调所有节点的状态,这通常需要加锁或者等待。加锁期间系统对外部请求无响应,可用性就下降了。

所以分布式系统通常无法做到强一致性,转而追求最终一致性------不要求数据在任何时刻都一致,但保证经过一段时间后,数据最终会达成一致。


三、分布式事务的常见方案

3.1 两阶段提交(2PC)

两阶段提交是最经典的强一致性方案。

它将事务提交分成两个阶段:第一阶段是准备阶段,事务协调者询问所有参与者是否能够提交事务,参与者执行操作但不提交,并锁定资源;第二阶段是提交或回滚阶段,如果所有参与者都同意,协调者通知大家提交,否则通知大家回滚。

两阶段提交能够保证强一致性,但代价很大。准备阶段锁定的资源会阻塞其他操作,影响并发性能。协调者本身是单点,如果协调者挂了,参与者可能一直处于锁定状态。因此,两阶段提交在实际微服务中很少使用。

3.2 TCC(Try-Confirm-Cancel)

TCC 是一种补偿型方案,将一个完整的业务操作拆成三个步骤。

Try 阶段尝试执行业务,预留资源但不真正提交。Confirm 阶段确认执行,使用 Try 阶段预留的资源真正提交。Cancel 阶段取消执行,释放 Try 阶段预留的资源。

以下单扣库存为例:Try 阶段检查库存是否充足,将库存状态改为"冻结中",数量减少但不真正扣除;Confirm 阶段将冻结的库存真正扣除;Cancel 阶段将冻结的库存释放,恢复原数量。

TCC 的优点是不需要锁定资源,并发性能好。缺点是需要写大量的补偿代码,业务侵入性强。

3.3 可靠消息最终一致性

这个方案利用消息队列实现跨服务的最终一致性。核心思路是:一个服务完成本地事务后,发送一条消息到消息队列,其他服务通过消费这条消息来完成自己的操作。

下单扣积分的典型流程:订单服务创建订单(本地事务),同时发送一条"订单已创建"消息。积分服务消费这条消息,为对应用户增加积分。

这里的关键问题是:如何保证本地事务和发送消息这两个操作的一致性?RocketMQ 的事务消息机制提供了标准解法:先发送半消息(消费者不可见),执行本地事务,根据本地事务结果决定提交或回滚半消息。如果本地事务成功,半消息变为可见,消费者才能消费。

这个方案的优点是吞吐量高、服务间解耦好。缺点是无法保证强一致性,只能达到最终一致性。

3.4 本地消息表

本地消息表是最终一致性方案的另一种实现,不依赖消息队列的高级特性。

核心思路是:在业务数据库里建一张本地消息表,业务操作和消息记录放在同一个本地事务中。然后一个轮询程序定时扫描消息表,将未发送的消息发送到消息队列。消息消费方处理完业务后,会回调一个确认接口,轮询程序收到确认后更新消息状态为已发送。

这个方案的优点是实现简单,不依赖消息队列的高级功能,本地事务天然保证业务和消息的一致性。缺点是需要额外开发轮询和重试逻辑,且消息可能重复发送消费端需要做幂等。

3.5 Saga

Saga 是一种长事务解决方案,适用于业务流程较长、涉及多个服务的情况。它将一个长事务拆分成一组本地事务序列,每个本地事务都有一个对应的补偿操作。

执行过程中,如果某个本地事务失败,Saga 会反向执行之前已成功事务的补偿操作,使数据回到一致状态。

Saga 分为两种模式:一种是协调模式,由一个中央协调器统一指挥各参与者;另一种是编排模式,每个参与者完成自己的事务后触发下一个参与者,没有中央协调器。

Saga 的优点是适合流程长、步骤多的业务,不锁定资源、并发性能高。缺点是补偿逻辑需要提前设计,没有隔离性------一个 Saga 执行过程中,其他事务可能看到中间状态。


四、各种方案的选择参考

场景 推荐方案 理由
并发要求低、强一致性必须保证 2PC 但微服务中很少用
并发高,业务代码可控 TCC 性能好,但开发量大
最终一致性可接受,耦合度低 可靠消息 最常用,推荐
不想引入复杂中间件 本地消息表 实现简单可靠
业务流程长、步骤多 Saga 适合长事务场景

五、核心原则

在设计分布式事务时,有几个原则值得参考。

尽量避免分布式事务:最好的分布式事务是没有分布式事务。设计系统时尽量划分清楚服务边界,让需要强一致的操作尽量落在同一个服务内部。比如把库存和库存日志放在同一个服务中。

能异步不强同步:很多场景不需要强一致性。例如下单后发优惠券,完全可以异步处理,用户晚几秒收到优惠券体验影响不大。用异步消息可以大大简化问题。

幂等性是基础:无论用什么方案,重试和补偿操作不可避免。所有暴露的接口都应该具备幂等性------同样的请求执行一次和执行多次效果相同。

监控和告警不可少:最终一致性意味着会有不一致的时间窗口。必须有监控手段可以及时发现长时间未达成一致的数据,并有人工补偿的预案。


六、总结

分布式事务是微服务架构中最复杂的课题,没有之一。

它的难度不在于技术实现,而在于理论限制------CAP 定理决定了在分布式系统中无法同时兼顾一致性和可用性。我们能做的不是消除分布式事务,而是在具体业务场景中选择合适的方案,在一致性、性能、可用性之间做出合理的取舍。

对于大多数业务场景,最终一致性已经足够。通过可靠消息、本地消息表等方案,可以在不牺牲太多可用性的前提下,保证数据最终会一致。

只有资金、库存等极少数对一致性要求极高的场景,才值得考虑 TCC 或 Saga 这类更复杂但一致性更强的方案。

选择一个复杂的方案之前,先问自己一个问题:这个业务真的需要强一致性吗?很多时候,答案是否定的。

相关推荐
郑州光合科技余经理10 小时前
同城O2O海外版二次开发实战:从支付网关到配送算法
开发语言·前端·后端·算法·架构·uni-app·php
AI木马人12 小时前
13.【多租户架构实战】如何让一个AI系统同时服务多个用户且数据完全隔离?(完整设计方案)
人工智能·架构
Element_南笙13 小时前
VGG网络-深度学习经典架构解析
网络·深度学习·架构
AI自动化工坊13 小时前
Cloudflare Project Think技术实践:零成本AI Agent部署架构深度解析
人工智能·架构·agent·cloudflare
Cory.眼14 小时前
若依(RuoYi)框架介绍
架构·前后端分离·若依架构
像我这样帅的人丶你还14 小时前
前端监控体系与实践:从错误上报到内存与 GC 观测
前端·javascript·架构
squarezw15 小时前
AI 跑 5 小时,我干 15 分钟:一次 feature 交付的杠杆实验
架构
丷丩15 小时前
从“失忆工具“到“智能助手“:GeoAI平台的Agent架构演进
人工智能·架构·gis·空间分析·geoai
uzong15 小时前
更简单的架构如何让我成为更好的高级开发者
后端·架构