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


*源码地址*

评论区给


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

相关推荐
LDR0066 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术6 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园6 天前
C++20 Modules 模块详解
java·开发语言·spring
swordbob6 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
源分享6 天前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
Luminous.6 天前
C语言--day30
c语言·开发语言
何以解忧,唯有..6 天前
Go语言循环语句详解:for、range与循环控制
开发语言·算法·golang
謓泽6 天前
C语言不是语法,是通往机器的地图。
c语言·开发语言
云水一下6 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
飞天狗1116 天前
零基础JavaWeb入门——第五课第二小节:九大内置对象 · 第2个:response(响应对象)
java·开发语言