本文目录导读:

这是一个非常核心且复杂的分布式系统问题。没有任何一种“万能”的方案能完美解决所有场景下的数据一致性,需要根据业务对一致性、性能、可用性的要求来权衡选择。
我们先明确核心矛盾:分布式事务要保证多个独立数据库或服务之间的数据状态最终(或实时)保持一致。
以下是目前主流的保证数据一致性的方案,从强一致性到最终一致性排列:
追求强一致性(ACID,牺牲部分可用性)
适用于金融、证券、库存扣减等对一致性要求极高的场景。
两阶段提交
这是最经典的强一致性协议,由一个协调者和多个参与者组成。
-
阶段一(准备阶段): 协调者询问所有参与者“能提交吗?”,参与者执行事务,写Undo/Redo日志,但不提交,返回“是”或“否”。
-
阶段二(提交阶段):
- 如果所有人都说“是”,协调者发“全局提交”命令。
- 如果有人说了“否”或超时,协调者发“全局回滚”命令。
-
优点: 理论上保证了强一致性。
-
缺点(致命):
- 同步阻塞: 在准备阶段,所有参与者都锁着资源(如数据库行锁),性能极差。
- 单点故障: 协调者挂了,所有参与者都会一直锁着资源。
- 脑裂风险: 协调者在发“提交”命令时挂了,有些参与者收到了提交,有些没收到,导致数据不一致(这也是2PC最大的问题)。
-
现状: 几乎不在高并发互联网场景使用,主要用于单机数据库的XA协议或特定内部系统。
追求最终一致性(BASE,牺牲强一致性,换取高性能)
适用于大多数互联网场景(如订单、社交、内容发布)。
TCC
TCC是一种补偿型事务,它对业务侵入性较强,但性能远高于XA。
它将一个分布式事务拆分为三个步骤:
- Try(预留资源): 检查业务资源可用性,并预留资源(如冻结库存、冻结账户余额)。
- Confirm(确认提交): 如果所有Try都成功,执行业务操作(实际扣减库存、实际扣款)。要求保证幂等性。
- Cancel(取消回滚): 如果任意Try失败,执行补偿操作,释放Try阶段预留的资源(解冻库存、解冻余额)。要求保证幂等性。
- 优点: 性能高,无全局锁,避免了XA的同步阻塞问题,可以定制化的补偿逻辑。
- 缺点:
- 开发成本极高: 每个业务接口都需要拆成Try/Confirm/Cancel三个方法。
- 空回滚: Try未执行就收到了Cancel请求。
- 悬挂问题: Cancel先于Try到达。
基于消息的最终一致性
这是目前最常用、最推荐的方案,核心思想是:只要保证本地事务和消息发送的原子性,就能通过异步消息达成最终一致。
-
本地消息表
- 在业务数据库里创建一张
本地消息表。 - 在一个本地事务中,同时执行“业务操作(插入订单)”和“向本地消息表插入一条待发送消息”。这两个操作要么全成功,要么全失败。
- 通过一个定时任务扫描
本地消息表中的待发送消息,发送给MQ。 - 下游消费者(如库存服务)消费消息并执行业务(扣库存)。
- 下游执行成功后,回调上游服务,更新本地消息表状态为“已处理”。
- 失败处理: 如果下游执行失败,可以重试,如果多次重试失败,可以转为人工介入。
- 在业务数据库里创建一张
-
优点: 简单可靠,无第三方依赖。
-
缺点: 需要维护本地消息表,对业务有侵入,且消息表可能成为数据库瓶颈。
-
RocketMQ 事务消息(推荐) 这是对本地消息表的优化,由MQ中间件实现。
- 半消息: 生产者向RocketMQ发送一条“半消息”(暂不可见)。
- 执行本地事务: 生产者执行本地业务(如创建订单),如果本地事务成功,提交半消息;如果失败,回滚半消息。
- 回查: 如果生产者宕机,RocketMQ会定期回调生产者的接口,询问“刚才那个本地事务到底成功了没?”,生产者根据数据库状态回复“提交”或“回滚”。
- 消息被成功提交后,下游消费者正常消费。
-
优点: 无业务侵入,无需建表,消息可靠。
-
缺点: 依赖RocketMQ,且理论上有短暂的不一致窗口期(半消息 -> 提交之间)。
Saga 模式
一种长事务解决方案,将一个长事务拆分成一系列子事务,每个子事务都有对应的补偿操作。
-
实现方式:
- 编排(Choreography): 各个服务通过监听彼此的事件来完成协调(如:订单服务 -> 支付服务 -> 库存服务)。
- 协调(Orchestrator): 一个中央协调器负责告诉各个服务“该做什么”,并处理失败后的补偿。
-
优点: 适合长事务、跨多个系统。
-
缺点: 补偿操作难度大,需要经验,协调器模式引入了单点。
如何选择?一张表格总结
| 方案 | 一致性级别 | 性能/吞吐 | 开发成本 | 典型场景 | 代表技术 |
|---|---|---|---|---|---|
| 2PC/3PC | 强一致性 | 极低 | 中 | 金融核心、跨库严格一致 | XA、Seata AT |
| TCC | 最终一致性 | 高 | 极高 | 高并发、敏感的扣款/扣库存 | Seata TCC |
| 本地消息表 | 最终一致性 | 中 | 中 | 传统、简单的异步解耦 | 自研 |
| RocketMQ 事务消息 | 最终一致性 | 极高 | 低 | 互联网主流选择(推荐) | RocketMQ |
| Saga | 最终一致性 | 高 | 高 | 长流程、跨组织、可回滚的复杂业务 | Seata Saga、Temporal |
核心建议
- 尽量不要使用分布式事务! 最好的解决方案是避免跨库/跨服务的强一致事务,通过数据冗余(如将用户信息复制到订单库)、去数据库依赖(从缓存/ES查询数据)等方式,让业务尽可能在一个数据库实例内完成。
- 能异步就别同步: 大部分业务可以接受“几秒后不一致”,用异步消息 + 重试机制解决99%的问题。
- 做好幂等性和最终补偿: 无论用哪种方案,所有操作(尤其消费消息和补偿)都必须是幂等的,设计好兜底方案,比如人工核对、定时对账、手动补偿脚本。
- 选成熟的框架: 如果必须用,使用 Seata 或 RocketMQ 这类成熟的中间件,不要自己造轮子。
最后总结:
- 性能第一,能接受短暂不一致? → RocketMQ 事务消息(首选)。
- 一致性要求极高,且性能不重要? → Seata AT(基于2PC优化)。
- 业务极其复杂,需要灵活补偿? → Seata TCC 或 Saga。
- 最简单的兜底方案? → 本地消息表 + 定时任务 + 重试 + 监控告警 + 人工介入。