分布式事务怎么选型?2PC、TCC、SAGA、本地消息表一次讲清

分布式事务怎么选型?2PC、TCC、SAGA、本地消息表一次讲清

分布式事务没有"最好"的方案,只有"最匹配业务"的方案。

选型的本质不是选技术,而是选一致性、性能、复杂度、团队承载力之间的取舍。


一、先问一句:你真的需要分布式事务吗?

很多"分布式事务问题",其实在架构阶段就能消灭:

  • 把数据放回到同一个库 → 本地事务搞定
  • 把强依赖改成事件驱动 → 异步最终一致
  • 把"必须一起成功"改成"先成功,再补偿"
  • 把服务边界重新划,让聚合根待在一个服务里

**能不用分布式事务,就不用。**​

一旦跨服务、跨库、跨团队,复杂度是指数级上涨。


二、四种方案一句话定性

方案 一句话 一致性 像什么
2PC / XA​ 数据库层强一致,大家先举手,再一起提交 强一致 开会全体表决
TCC​ 业务层两阶段:先冻住,再确认/释放 业务层准强一致 预定座位
SAGA​ 长流程拆成多步,失败了反向补偿 最终一致 工单流转
本地消息表​ 业务+消息同事务,后台投递,下游幂等消费 最终一致 Outbox 模式

三、2PC / XA:强一致,但代价很重

原理

  1. 协调者发 prepare
  2. 各参与者锁资源、预执行、投票
  3. 全 OK → commit,否则 rollback

优点

  • 数据库原生支持(MySQL XA、PostgreSQL 2PC)
  • 应用代码侵入低
  • 一致性最强

缺点

  • Prepare 阶段持锁,并发一高就堵
  • 协调者单点,挂了参与者干等
  • 跨服务、跨语言、跨中间件时很难搞
  • 高并发互联网业务基本扛不住

适合

  • 单体多数据源
  • 传统金融核心、账务系统
  • 低并发、短事务、强一致要求极高

不适合

  • 高 QPS 下单链路
  • 长流程业务
  • 跨团队微服务

结论:2PC 是保底方案,不是默认方案。


四、TCC:资金类场景的"手工强一致"

三个动作

  • Try:预留资源(冻结金额、预扣库存)
  • Confirm:真正提交(扣钱、扣库存)
  • Cancel:释放预留(解冻、回补)

优点

  • 不靠数据库锁,性能比 2PC 好
  • 资源可预留,适合资金、库存、额度
  • 一致性比 SAGA 更可控

缺点

  • 业务侵入极高:每个接口要写三套逻辑
  • 要处理三大坑:
    • 空回滚:Cancel 来了,但 Try 没成功
    • 悬挂:Cancel 先到,Try 后到
    • 幂等:Confirm/Cancel 被重复调用
  • 开发成本是本地消息表的 3 倍以上

适合

  • 支付扣款
  • 账户转账
  • 库存预占
  • 优惠券/积分冻结

不适合

  • 小团队
  • 流程经常变
  • 业务动作不可撤销

结论:TCC 不是"高级微服务标配",而是资金链路的重武器。


五、SAGA:长事务和复杂流程的解药

原理

把大事务拆成多个本地事务:

复制代码
创建订单 → 扣库存 → 扣积分 → 通知物流

某一步失败,就按反方向补偿:

复制代码
取消订单 → 回补库存 → 回退积分 → 取消物流

两种写法

  • 编排式 Orchestration:有个中心协调器
  • 协同式 Choreography:大家靠事件互相触发

优点

  • 无长锁,适合长流程
  • 比 TCC 开发成本低
  • 订单、履约、出行、预订类业务非常贴

缺点

  • 没有隔离性 :中间状态对外可见
    • A 扣了钱,B 还没加钱,别的请求能看到"钱少了"
  • 补偿不一定能成功
    • 短信发过了、积分花掉了、票已经出票了
  • 要设计状态机、补偿链、人工兜底

适合

  • 电商下单履约
  • 机票/酒店/打车预订
  • 开户、审批、物流调度

不适合

  • 要求"别让别人看到中间状态"
  • 步骤不可逆
  • 团队没做过状态机

结论:SAGA 解决"流程长",不解决"强一致"。


六、本地消息表:最土,但最稳

原理

复制代码
BEGIN;
UPDATE orders SET status = 'PAID' WHERE order_id = ?;
INSERT INTO outbox_messages(message_id, topic, payload, status)
VALUES (?, 'order.paid', ?, 'PENDING');
COMMIT;

然后:

  1. 定时任务扫 PENDING
  2. 发到 MQ
  3. 成功改 SENT
  4. 下游消费必须幂等

这就是经典的 Transactional Outbox。

优点

  • 只依赖数据库,不依赖高级 MQ 特性
  • 业务侵入低
  • 实现简单、可观测、可运维
  • 中小团队也能维护住

缺点

  • 有延迟(扫表间隔 + MQ 延迟)
  • 只保证最终一致
  • 消息表会变大,要归档
  • 下游必须做幂等

适合

  • 订单创建后发通知
  • 积分发放
  • 日志/审计/埋点
  • 跨服务异步解耦

不适合

  • 必须同步返回最终结果
  • 资金扣减这种不能等一等的场景

结论:80% 的互联网业务,本地消息表 + 幂等 + 对账 就够了。


七、横向对比表

维度 2PC TCC SAGA 本地消息表
一致性 强一致 业务层准强一致 最终一致 最终一致
性能 差 中高 高 高
延迟 低 低~中 中 秒级
业务侵入 低 很高 中 低
开发成本 低~中 很高 中高 低
隔离性 有 有 无 无
运维复杂度 中 高 中 低
适合并发 低 中高 中高 中
典型场景 金融核心、短事务 支付、库存预占 订单履约、长流程 通知、积分、异步解耦

八、选型决策树(直接照着问)

1. 能不能收回到单库 / 单服务?

能 → 本地事务

别上分布式事务。

2. 能不能接受"暂时不一致,后面补齐"?

不能 → 去 3

能 → 去 4

3. 是否资金/库存/额度,且资源可预留?

是 → TCC

否,但必须强一致、并发低 → 2PC / XA

4. 是长流程、多步骤、可补偿吗?

是 → SAGA

否,只是"做完一件事,通知另一个服务也做点事" → 本地消息表 / 事务消息


九、工程落地里的"通用铁律"

不管选哪个,下面几条都逃不掉:

1. 幂等是第一优先级

用:

  • 业务唯一键(order_id、tx_id)
  • 消费记录表
  • 唯一索引
  • 乐观锁 version

消息队列基本都是"至少一次投递",重复是常态。

2. 补偿要可重试

补偿接口必须:

  • 幂等
  • 可重试
  • 有日志
  • 有告警
  • 有死信 / 人工介入通道

3. 最终一致必须有对账

别信"消息不会丢"。

要做:

  • 订单状态 ↔ 库存状态
  • 账户流水 ↔ 订单状态
  • 消息表 ↔ MQ 投递状态

系统自己恢复不了时,人能介入。

4. 别把所有链路都搞成分布式事务

真实系统里通常是混用:

复制代码
下单创建订单         → 本地事务
扣库存               → TCC / SAGA
支付                 → TCC
发积分               → 本地消息表
发短信 / 推消息       → 本地消息表 / 最大努力通知
订单履约长流程        → SAGA

强一致只用在"错了会资损"的地方。

其他地方,最终一致就是工程正义。

相关推荐
Eloudy3 小时前
ASTRA-sim 3.0:通过高保真GPU和基础设施建模进行下一级分布式机器学习模拟
人工智能·分布式·机器学习
泡海椒3 小时前
JQuick-SQL 模块架构:从 SQL AST 到分布式执行
分布式·sql·架构
starzy19904 小时前
深度拆解 Kafka 源码:Producer 构造器装配了哪些核心组件
分布式·kafka
程序员大阳5 小时前
副队长大数据教程(7)--Hadoop 集群角色配置逻辑详解
大数据·hadoop·分布式·集群·配置·角色·逻辑
蓝胖的四次元口袋5 小时前
RabbitMQ知识梳理(2)
分布式·rabbitmq
xianyuCcCcCCCcc20 小时前
Ceph 分布式存储:核心架构与集群部署
分布式·ceph·架构
真上帝的左手21 小时前
10. 软件设计&架构-分布式文件存储系统
分布式·架构·应用架构
Thomas.Sir1 天前
第30课:分布式事务解决方案 & Seata最终一致性实战
分布式·spring cloud·seata
donk13141 天前
React Native for OpenHarmony 实战:三方库 react-native-circular-action-menu 的鸿蒙化适配指南
javascript·分布式·react native·react.js·wpf·harmonyos