当前位置: 首页 > news >正文

Saga 分布式事务:你以为的最终一致性,其实是个慢动作炸弹

我曾负责过一个订单系统,号称用了 Saga 模式做分布式事务。上线第三天就出事了:用户支付成功后,订单状态卡在"待支付"——钱扣了,订单没更新。

排查了两天,最后发现是 Saga 协调器在补偿阶段崩了,但补偿消息已经发出去了。下游服务(库存服务)收到补偿消息后回滚了库存,但订单服务没收到补偿确认(Kafka 消息丢失),就一直卡着。

这就是 Saga 模式在生产环境的真实面貌:理论上能跑,实际上每个环节都可能掉链子

Saga 模式的核心设计

Saga 模式把分布式事务拆成多个本地事务,每个本地事务都有对应的"补偿事务":

T1 (创建订单) → T2 (扣库存) → T3 (扣款) → 完成 ↓ 失败 C2 (恢复库存) ← C1 (取消订单) ← 触发补偿

两个核心角色:

  • 协调器(Orchestrator):控制整个 Saga 流程,决定下一步是执行还是补偿
  • 参与者(Participant):执行具体业务逻辑,发布事件

设计模式层面的真相

Saga 模式本质上是状态机模式 + 观察者模式 + 责任链模式的组合应用:

publicclassSagaStateMachine{privateSagaStatecurrentState=SagaState.STARTED;privateList<Step>steps=newArrayList<>();privateList<Compensation>compensations=newArrayList<>();publicvoidexecute(){for(Stepstep:steps){try{step.execute();}catch(Exceptione){compensate();return;}}}privatevoidcompensate(){// 责任链模式:倒序执行补偿Collections.reverse(compensations);for(Compensationc:compensations){c.execute();}}}

看上去很优雅。但生产环境里,状态机会因为各种原因卡住

Saga 模式的四个真实陷阱

陷阱 1:补偿操作不是幂等的

T1 创建订单,T1 失败需要补偿"取消订单"。但如果"取消订单"这个补偿操作执行了一半崩了呢?

publicvoidcompensateCreateOrder(LongorderId){Orderorder=orderRepository.findById(orderId).orElseThrow();order.setStatus(OrderStatus.CANCELED);// 步骤 1orderRepository.save(order);// 步骤 2:崩在这里notificationService.sendCancelNotify(order);// 步骤 3}

如果步骤 2 崩了,步骤 3 没执行,下次重试时步骤 1 是幂等的(setStatus重复执行无害),但步骤 3 可能重复发通知——用户收到 3 条"订单已取消"的短信。

解决方案:每个补偿操作都要设计成幂等,用唯一键 + 状态机:

publicvoidcompensateCreateOrder(LongorderId){Orderorder=orderRepository.findById(orderId).orElseThrow();if(order.getStatus()==OrderStatus.CANCELED){return;// 幂等:已取消直接返回}order.setStatus(OrderStatus.CANCELED);orderRepository.save(order);// 通知用消息表去重,不在这里直接发outboxRepository.save(newNotificationOutbox(orderId,"CANCELED"));}

陷阱 2:隔离性缺失导致"脏读"

Saga 没有 ACID 中的隔离性。如果两个 Saga 同时修改同一个订单:

Saga A: 订单状态 PENDING → PAID Saga B: 订单状态 PENDING → CANCELED

两个 Saga 并发执行,A 把订单改成 PAID 后崩溃,触发补偿(订单改回 PENDING)。但这时 B 已经把订单改成 CANCELED 了。A 的补偿操作setStatus(PENDING)覆盖了 B 的setStatus(CANCELED)B 看到的状态是错的

这就是经典的"丢失更新"问题。Saga 模式天生没有隔离性,必须用应用层补偿:

// Saga A 的补偿publicvoidcompensatePay(LongorderId){Orderorder=orderRepository.findById(orderId).orElseThrow();if(order.getStatus()==OrderStatus.CANCELED){return;// B 已经处理了,A 的补偿跳过}order.setStatus(OrderStatus.PENDING);orderRepository.save(order);}

但这个判断本身就可能出错(如果还有 Saga C 也在改这个订单呢?)。Saga 模式的隔离性问题,本质上是无解的,只能用业务规则尽量减少并发冲突

陷阱 3:消息可靠投递的复杂性

Saga 依赖消息传递(Kafka/RabbitMQ)来推进状态机。但消息可能丢失、重复、顺序错乱。

最常见的事故:用户支付成功后,订单服务发了"支付成功"事件给下游,但 Kafka 那次写入失败。协调器没收到事件,整个 Saga 卡住。

解决:用事务性 outbox 模式

@TransactionalpublicvoidpayOrder(LongorderId){Orderorder=orderRepository.findById(orderId).orElseThrow();order.setStatus(OrderStatus.PAID);orderRepository.save(order);// 业务表和 outbox 表在同一个事务里OutboxMessagemsg=newOutboxMessage();msg.setTopic("order.paid");msg.setPayload(orderId.toString());outboxRepository.save(msg);}

后台有个 poller 进程不断扫描 outbox 表,把消息发到 Kafka。发送成功后标记为已发送。如果 poller 崩了,重启后从 outbox 表继续发送

但 outbox 模式又带来新的问题:消息顺序性、重复消费、下游幂等。每解决一个问题,就引入两个新问题

陷阱 4:长时间运行的 Saga 状态爆炸

一个复杂的业务 Saga 可能涉及 7、8 个步骤,每个步骤都有"成功/失败/补偿中/补偿失败"四种状态。状态机的状态数会爆炸到 2^N:

STARTED → T1_DONE → T2_DONE → T3_FAILED → COMPENSATING ↓ T1_COMPENSATED → T2_COMPENSATING ↓ COMPENSATION_FAILED → 人工介入

每加一个步骤,状态空间翻倍。生产环境里,Saga 状态机的状态数很快就会超过 50 种,调试极其困难。

那为什么还要用 Saga?

因为两阶段提交(XA)在高并发场景下完全不可用

  • 协调者单点故障
  • 同步阻塞导致性能极差
  • 数据库连接被锁住,吞吐量降为 1/N

而 TCC(Try-Confirm-Cancel)需要业务方写三套方法,开发成本是 Saga 的 2-3 倍

Saga 在"最终一致性"和"开发成本"之间找了个平衡点。但生产环境用 Saga,你必须接受以下几个事实

  1. 状态会卡住,必须有人工介入通道(运营后台强制推进状态)
  2. 必须有对账系统(每天定时跑一遍,找出不一致的状态)
  3. 必须有业务补偿机制(状态卡住时,业务上怎么处理——退款?重试?人工?)
  4. 监控和告警必须覆盖每一个步骤的耗时(某个步骤慢 5 秒,整个 Saga 就会慢 5 秒)

一句话总结

Saga 模式是分布式事务的"次优解",不是"最优解"。它用状态机换来了性能,但代价是失去了隔离性 + 引入了消息复杂性 + 状态空间爆炸

如果你正在设计分布式事务,先问自己三个问题

  1. 业务真的需要分布式事务吗?能不能改成事件驱动 + 最终一致性?
  2. 能不能用本地消息表 + 单服务事务搞定?
  3. 如果非用 Saga,状态机怎么设计?补偿操作怎么幂等?消息怎么可靠投递?

答不上来就别用 Saga,老老实实用本地事务 + 异步消息,业务上 90% 的"分布式事务"问题根本不存在。

http://www.gsyq.cn/news/1539821.html

相关文章:

  • 华硕笔记本性能优化神器的惊艳体验:G-Helper深度评测与效率革命
  • 如何解决趋势线的滞后问题(下):LLT 实战法则与 8 年回测表现
  • ControlNet-v1-1_fp16_safetensors终极指南:精准控制AI图像生成的艺术
  • 镜像视界(浙江)科技有限公司耿文海个人简介
  • PIC单片机驱动LCD模块:从硬件连接到驱动编程的嵌入式入门实践
  • 暮云南壹府多少钱?价格与口碑综合考量 - mypinpai
  • OneReward:基于多任务人类偏好学习的统一掩码引导图像生成
  • WebRTC AV1视频编码介绍:下一代编码格式在实时通信中的应用
  • 2026年靠谱过炉治具清洗机怎么选?官方甄选与行业分析指南 - 优质品牌商家
  • mysql数据库应用②
  • 从 0 到 1 入门 Web 渗透测试 学习复盘精简总结
  • 如何快速上手MediaInfo:视频音频文件信息检测的完整教程
  • 2026年做高效送风口的靠谱公司有哪些 - 品牌排行榜
  • 业务流程自动化怎么落地?企业从0搭建完整路径(RPA+智能体全流程解析)
  • 2026年五金表面处理服务商甄选指南:靠谱的滚喷漆与电泳加工怎么选? - 优质品牌商家
  • 如何快速掌握开源计时工具LiveSplit:新手完全指南
  • 2026年工业型瓜果削皮机生产商甄选:哪些品牌值得关注? - 优质品牌商家
  • 分组聚合不是代码操作,而是业务认知手术
  • 青岛漏水检测维修权威推荐:卫生间-厨房-阳台-屋顶天花板漏水维修:靠谱防水补漏公司团队TOP5推荐(2026最新深度调研实测榜单) - 即刻修防水
  • GLM-5自主Agent实战:上下文切片与工具调度的工程化实现
  • SecureCRT连接Linux文件无颜色?终端颜色显示原理与配置全解析
  • 嵌入式测试学习第 37 天:异常场景测试:断电、拔插、干扰、非法指令
  • 告别无效监测!这款 GEO 工具,同时满足新手入门 + 企业专业运营
  • 从日系书法到中文美学:霞鹜文楷如何重塑开源中文字体生态
  • 如何评估工业电剪刀:刀头不用频繁换的品牌推荐 - 工业品牌热点
  • JMeter性能测试实战:从工具使用到瓶颈定位的完整指南
  • 2026年国内泡沫箱生产厂家推荐甄选:加厚、冷链、生物医用领域优质供应商分析 - 优质品牌商家
  • 2026年山特不间断电源TOP5推荐:山特工业UPS电源、山特蓄电池、恒安UPS电源、恒安高频UP电源、施耐德UPS电源选择指南 - 优质品牌商家
  • 用Python和AI将YouTube评论聚类生成影评
  • 2026靠谱的礼盒定制厂家排名,翊佳包装上榜 - mypinpai