每日一 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小程序搜索"加班计")


*源码地址*

评论区给


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

相关推荐
T量子永生1 分钟前
Android SISO 分布式编译集群配置方案
android·分布式·云计算·源代码管理
波力海苔夹心脆6755 分钟前
C# 委托与事件详解:委托声明、Action/Func、多播、event 与 EventHandler 入门教程
开发语言·c#·.net·上位机·委托·事件
Dovis(誓平步青云)23 分钟前
切换语言后按钮被截断,本地化审计怎样发现长度风险
开发语言·前端·javascript·人工智能·机器学习·华为·harmonyos
小王C语言32 分钟前
Qt常用控件:按钮类控件,普通按键、单选、复选
开发语言·qt
街头便衣34 分钟前
OpenRouter周榜复算:164万亿token,输出2.55%,DeepSeek账单占1.4%
android·java·开发语言·人工智能·语言模型·开源
老王IT41 分钟前
PHP语言基础语法介绍
开发语言·php
2501_944521599 小时前
Ramda 跨端三方库仓颉适配全流程
开发语言·javascript·flutter·php
行思理9 小时前
go语言学习难度及适合学习的人群
开发语言·学习·golang
wangchunyu11411 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python