为什么微服务不要随便用分布式事务

一句话速记

分布式事务的代价是:性能下降(协调开销、锁等待)+ 可用性降低(任一参与者故障则整体阻塞)+ 复杂度激增(幂等、补偿、回滚逻辑)。互联网业务 99% 的场景可以用领域边界拆分(把需要强一致的数据放同一服务)+ 最终一致性 + 业务幂等替代,只有真正必要时(金融核心)才用 TCC。

关键细节

1)分布式事务的真实代价

性能代价

单机事务(本地):
  BEGIN → 业务逻辑 → COMMIT(毫秒级)

2PC 分布式事务:
  协调者向 N 个参与者发 Prepare(N 次网络往返)
  等待所有回复(等最慢的那个)
  协调者发 Commit(N 次网络往返)
  
  总延迟 = 本地事务延迟 × 2 + 2 × N × 网络 RTT
  10ms + 2 × 3 × 5ms = 10 + 30 = 40ms(4x 延迟)
  
TCC 三次业务调用(Try + Confirm + Cancel)
  每次都有网络 RTT + 业务处理时间
  粗略估算:3 × (业务处理 + 网络RTT) = 延迟 3x

可用性代价

可用性 = 所有参与者可用性的乘积

假设:每个服务 SLA = 99.9%(3 个九)
2PC 涉及 3 个服务:
  0.999 × 0.999 × 0.999 = 0.997 ≈ 99.7%(两个半九)
  单月不可用时间:43.2 分钟 → 129.6 分钟(3x 增加)

TCC 同理:涉及服务越多,整体可用性越低

2)何时需要分布式事务(真正的需求)

✅ 真正需要:
  金融核心:转账(扣 A 加 B 必须原子,否则钱凭空消失)
  库存超卖:下单扣库存 + 创建订单 必须原子(超卖是业务灾难)
  
⚠️ 可以用最终一致:
  订单 + 支付 + 物流(三个系统):允许临时不一致(补偿可恢复)
  用户注册 + 积分发放:允许积分延迟几秒到账
  消息通知:允许消息延迟
  
❌ 不需要:
  纯读操作(查询不需要事务)
  幂等操作(重复执行无副作用)
  独立业务(不跨服务的操作)

3)替代方案:领域边界设计

问题根源:微服务拆分过细,导致一个业务操作跨多个服务

解法 1:合并服务(最直接)
  如果两个数据必须强一致 → 它们可能属于同一个领域边界
  把"订单"和"库存"放同一个服务 → 本地事务解决

解法 2:数据复制 + 最终一致
  库存服务 → 发事件到 MQ → 订单服务消费(最终一致)
  缺点:有一致性窗口(几十毫秒到几秒)
  适合:非强实时场景

解法 3:事件驱动架构(Event-Driven)
  操作改为"发布领域事件"
  各服务订阅事件,各自处理(幂等)
  最终一致,无中心协调者
  
解法 4:Saga 编排(长流程)
  将长事务拆为独立步骤 + 补偿
  容忍临时不一致(但最终一致)

4)最终一致性的工程实现

原则:Embrace Eventual Consistency(拥抱最终一致性)

做到最终一致需要:
  1. 业务操作幂等(重复执行 = 执行一次)
  2. 消息至少一次投递(At-Least-Once)
  3. 对账机制(定时扫描不一致,自动修复)
  4. 人工介入兜底(极少数无法自动修复的情况)

对账系统(Reconciliation):
  定时任务(如每小时):对比 A 服务数据和 B 服务数据
  发现不一致 → 发告警 + 自动补偿(如果可以)→ 推人工处理(如果无法自动)
  
  例:支付对账
  支付宝每天发送对账文件(全量 / 差异)
  商户系统比对:多付 → 退款;少付 → 追收;一致 → 正常

5)CAP 理论的工程解读

CAP:Consistency(一致性)+ Availability(可用性)+ Partition Tolerance(分区容忍)
三者只能同时满足两个

实际互联网系统:网络分区不可避免(P 必须有)
所以选择:
  CA(不可能):忽略 P,单机系统才行
  CP(一致性 + 分区容忍):牺牲可用性(ZooKeeper 选 CP)
  AP(可用性 + 分区容忍):牺牲强一致(Eureka 选 AP,Redis 默认 AP)

BASE 理论(互联网的实用版):
  Basically Available(基本可用)
  Soft State(软状态,允许临时不一致)
  Eventually Consistent(最终一致)
  → 分布式系统工程实践的指导原则

延伸追问

  • Q:你在项目中怎么避免分布式事务的? → 首先通过领域建模,把强一致的数据放同一服务(同一 DB,本地事务);其次通过业务梳理,确认哪些场景真的需要强一致(通常比想象少);对于跨服务的场景,用本地消息表 + MQ 保证最终一致,消费端幂等处理重复消息;定时对账任务发现和修复不一致。
  • Q:TCC 框架(Seata)用起来容易吗? → Seata AT 模式(自动,侵入性低):不需要写 Try/Confirm/Cancel,框架自动生成 undo log,业务侵入性低,适合无法改造旧代码的场景;TCC 模式:需要自己实现三个方法,侵入性高,但控制粒度更细,性能更好。实际落地需要团队有较强分布式事务经验,otherwise 引入复杂度 > 解决的问题。
  • Q:最终一致性下,用户看到”临时不一致”怎么处理(用户体验)? → 产品层面:显示”处理中”状态,给用户预期管理(“积分将在 5 分钟内到账”);技术层面:减少一致性窗口(优化 MQ 消费延迟);补偿层面:用户投诉时有快速修复通道(客服工具)。绝大多数用户不会在 5 秒内刷新页面查看是否一致。

我的记法

  • 分布式事务 = 延迟 3~4x + 可用性下降 + 复杂度 × N
  • 替代:领域边界收敛(强一致放同一服务)+ 最终一致 + 幂等 + 对账
  • CAP 选 AP + BASE 思想 = 互联网标准答案
  • 真正需要:金融转账(用 TCC),其他尽量避免
  • 一句话:「分布式事务是最后手段,先问能不能用最终一致+幂等替代」

状态

  • 已背速记
  • 能解释 CAP + BASE
  • 能说替代分布式事务的三种设计思路