每日一 Go-72、分布式事务 & 一致性:本地消息表、事务消息、SAGA、TCC怎么选?

单机事务靠数据库,分布式事务靠"妥协 + 设计"。比如一个经典的业务

js 复制代码
下单 → 扣库存 → 扣余额 → 发货

拆成多个服务后,会有以下问题:

  • 数据库事务失效了

  • BEGIN/COMMIT 再也管不住全局

  • 网络、宕机、重试,任何一步都可能出错。

一、为什么分布式事务这么难?

在单体应用中:

java 复制代码
一个进程 + 一个数据库 = ACID
ACID = Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)

在微服务中:

java 复制代码
多个服务 + 多个数据库 + 网络 = Chaos
Chaos = 系统不再是确定性的,而是充满不可预测失败的状态空间

CAP定理告诉我们:分布式系统中,一致性(C)、可用性(A)、分区容错(P)不可兼得。

现实系统里:

  • P必须要

  • 大多数业务会在C和A之间权衡

二、本地消息表(最常用,最稳)

  1. 思想:业务操作+记录消息,在同一个本地事务里完成;然后:异步任务扫描消息表,发送 MQ/HTTP,成功后标记已发送。

  2. 流程图

sql 复制代码
BEGIN
  插入订单
  插入消息表(pending)
COMMIT

后台任务:
  扫描 pending 消息
  发送消息
  成功 → 标记 done
  1. 示例
go 复制代码
type Order struct {
	ID     int64
	Amount int64
}

type Message struct {
	ID      int64
	Topic   string
	Payload string
	Status  string // pending / done
}
go 复制代码
func CreateOrder(db *sql.DB, amount int64) error {
	tx, err := db.Begin()
if err != nil {
return err
	}

// 1. 创建订单
	_, err = tx.Exec(
"insert into orders(amount) values(?)",
		amount,
	)
if err != nil {
		tx.Rollback()
return err
	}

// 2. 写本地消息表
	_, err = tx.Exec(
"insert into messages(topic, payload, status) values(?, ?, ?)",
"order_created",
"{...}",
"pending",
	)
if err != nil {
		tx.Rollback()
return err
	}

return tx.Commit()
}
  1. 优缺点

优点:简单;不依赖MQ;极其稳定。

缺点:有延迟;要自己写消息投递和重试逻辑。

适合业务:订单、支付、通知类业务。

三、事务消息(RocketMQ 思路)

  1. 核心思想:先发"半消息",再提交本地事务,最后确认消息。

  2. 三阶段

markdown 复制代码
1. 发送 Half Message(MQ 不投递)
2. 执行本地事务
3. Commit / Rollback 消息
  1. 示例
go 复制代码
func SendTxMessage() error {
// 1. 发送半消息
	msgID := mq.SendHalf("order_created", payload)

// 2. 执行本地事务
	err := createOrder()
if err != nil {
		mq.Rollback(msgID)
return err
	}

// 3. 提交消息
	mq.Commit(msgID)
return nil
}
  1. 优缺点

优点:消息与业务强绑定;不需要扫描表。

缺点:依赖MQ;心智成本高。

适合业务:已经深度使用RocketMQ的系统。

心智成本 = 一个系统对"人"的复杂度

包括:学习成本、理解成本、使用成本、维护成本、排错成本、接手成本

四、SAGA:长事务的现实解法

  1. 核心思想:把一个大事务,拆成多个本地事务 + 对应的补偿操作

  2. 示例流程

http 复制代码
T1: 创建订单        → C1: 取消订单
T2: 扣库存          → C2: 回滚库存
T3: 扣余额          → C3: 回滚余额
  1. 示例
go 复制代码
func SagaOrder() error {
if err := createOrder(); err != nil {
return err
	}

if err := deductStock(); err != nil {
		cancelOrder()
return err
	}

if err := deductBalance(); err != nil {
		restoreStock()
		cancelOrder()
return err
	}

return nil
}
  1. 优缺点

优点:不需要锁;可扩展性好;非常适合微服务。

缺点:补偿逻辑复杂;中间态暴露。

适合业务:电商、长流程业务

五、TCC:最"强"的分布式事务

  1. 三个阶段

|---------|------|
| 阶段 | 含义 |
| Try | 预留资源 |
| Confirm | 真正提交 |
| Cancel | 回滚释放 |

  1. 示例
go 复制代码
func TryDeduct(stock int) error {
// 冻结库存
return nil
}

func ConfirmDeduct(stock int) error {
// 扣减冻结库存
return nil
}

func CancelDeduct(stock int) error {
// 释放冻结库存
return nil
}
  1. 优缺点

优点:接近强一致;状态可控。

缺点:代码量大;对业务入侵极强。

适合业务:资金、交易、金融系统。

六、四种方案对比

|-------|------|-------|--------|
| 方案 | 一致性 | 复杂度 | 常见场景 |
| 本地消息表 | 最终一致 | ⭐⭐ | 订单、通知 |
| 事务消息 | 最终一致 | ⭐⭐⭐ | MQ驱动系统 |
| SAGA | 最终一致 | ⭐⭐⭐ | 长流程 |
| TCC | 强一致 | ⭐⭐⭐⭐⭐ | 金融 |


友情链接:加班费计算器(vx小程序搜索"加班计")


*源码地址*

评论区给


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!

相关推荐
董可伦9 分钟前
Spark 源码 | SparkSubmitArguments 参数解析(三)
大数据·分布式·spark
逝水无殇33 分钟前
C# 正则表达式详解
开发语言·后端·正则表达式·c#
朱永博1 小时前
使用Onnruntime实现Padim/Patchcore推理
开发语言·c#
前端H1 小时前
Biome & Rolldown:Rust 工具链接管前端基建
开发语言·前端·rust
天天进步20151 小时前
UI-TARS 源码解析 #4:UI-TARS 与传统 RPA 的区别:为什么它不是简单的坐标点击脚本?
开发语言
运维行者_1 小时前
广域网性能监控:分布式IT架构下的链路质量保障
开发语言·网络·分布式·后端·架构·数据库架构
梅雅达编程笔记1 小时前
零基础学 Python 第7章 | 字典 dict:键值对存储
开发语言·python·beautifulsoup·numpy·pandas
灯澜忆梦1 小时前
GO---map函数
开发语言·前端·后端·golang
她说可以呀2 小时前
RabbitMQ Confirm与Returns模式
分布式·rabbitmq
北冥you鱼2 小时前
Go语言与默克尔树:区块链数据完整性的基石
开发语言·golang·区块链