← 返回博客
go2026-09-12 22:13:282 分钟 · 451 0

分布式事务与最终一致性:Saga 与事务消息

跨服务的写操作没法用本地事务,讲清 Saga 补偿模式、事务消息(本地消息表/事务消息队列)怎么保证最终一致,以及什么时候该用。

#gin#分布式事务#Saga

1. 为什么本地事务不够

下单要扣库存、加积分、创订单,三个服务各管各的库。一个 @Transactional 管不了三个库。跨服务要么两阶段提交(CP 但锁资源、可用性差),要么最终一致(AP,推荐)。

2. Saga:正向步骤 + 补偿

把大事务拆成一串本地事务,每步有对应的补偿(回滚)操作:

steps := []SagaStep{
    {Do: deductStock, Compensate: restoreStock},
    {Do: createOrder, Compensate: cancelOrder},
    {Do: addPoints, Compensate: removePoints},
}
// 任一步失败,反向执行前面已完成的补偿

扣库存成功、建订单失败,就调 restoreStock 把库存加回去。

3. 用事件编排 Saga

更松耦的写法:每步完成发事件,下一步订阅后执行,失败时发补偿事件:

js.Publish("stock.deducted", evt)
// 订单服务订阅,建订单;失败则发 order.failed → 库存订阅补偿

编排(集中)还是协同(事件),看复杂度。步骤多、要集中可视选编排;想解耦选协同。

4. 事务消息

保证「本地落库」和「发事件」同时成功,不丢事件:

// 本地消息表:同一事务写业务表和消息表
tx.Exec("INSERT order ...")
tx.Exec("INSERT outbox (topic,payload) ...")
tx.Commit()
// 独立 worker 扫 outbox 发消息,发成功删记录

发消息失败重试,保证下游最终收到。Kafka 的事务消息也能做类似语义。

5. 读已写一致

最终一致下,下单后立刻查可能查不到积分。前端要么接受延迟,要么用「下单成功后轮询 / WebSocket 推送完成」(第 11 篇)代替立即查。

6. 上线清单

能本地事务解决的别上分布式。优先最终一致(Saga/事务消息),少碰 2PC。补偿操作必须幂等,可能被执行多次。Saga 步骤设计成可补偿、可重试。用 outbox 模式保证事件不丢。监控 Saga 卡住的实例,人工介入。

下一篇讲注册发现与配置中心,服务多了怎么管。

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/203