分布式事务解决方案:最佳实践(零基础入门)
大家好,我是一名开源项目维护者,也经常在社区做技术分享。最近很多初学者问我:“分布式事务到底该怎么学?面试题老是问,但书上讲得太抽象。”
我当初学的时候,也是被“两阶段提交”、“TCC”这些词绕得头晕。今天,我就用最实践的方式,带你从零开始搞懂分布式事务——不用高深理论,只写能跑的代码。
什么是分布式事务?为什么需要它?
想象一下:你在一个电商系统下单,要同时扣库存、创建订单、扣用户余额。这三个操作分别在三个不同的数据库或服务中。
- 如果扣了库存,但订单没创建成功,钱没扣,那库存就白扣了!
- 如果订单创建了,但余额没扣,商家就亏了!
分布式事务就是保证这些跨服务的操作“要么全成功,要么全失败”。这就是我们常说的 ACID 中的 原子性(Atomicity)。
环境准备:5分钟搭好开发环境
我们将用 Go 语言来演示(Go 在微服务和高并发场景中非常流行,也是大厂面试常考语言)。请确保你已安装:
- Go 1.20+(官网下载)
- Docker(用于快速启动本地数据库)
- 任意代码编辑器(推荐 VS Code + Go 插件)
执行以下命令验证环境:
go version # 应输出 go1.20.x
docker --version # 应输出 Docker version x.x.x
接下来,我们用 Docker 启动两个独立的 MySQL 实例,模拟两个服务:
# 启动第一个数据库(订单服务)
docker run -d --name order-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql:8
# 启动第二个数据库(账户服务)
docker run -d --name account-db -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3307 mysql:8
💡 提示:3307 是映射到宿主机的端口,容器内还是 3306。
核心概念:三种主流方案对比
分布式事务没有“银弹”,不同场景用不同方案。以下是三种最常用的:
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 2PC(两阶段提交) | 协调者先问所有参与者“能提交吗?”,都同意后再正式提交 | 强一致性 | 性能差、阻塞、单点故障 | 对一致性要求极高的金融系统 |
| TCC(Try-Confirm-Cancel) | 业务层面实现“预留-确认-取消”三步 | 高性能、灵活 | 开发复杂、需改造业务逻辑 | 订单、支付等核心链路 |
| Saga 模式 | 每个服务执行本地事务,并定义补偿操作(回滚) | 无锁、易扩展 | 最终一致性、补偿逻辑复杂 | 长流程业务(如旅行预订) |
📌 面试题高频考点:
“2PC 和 TCC 的区别?”
答:2PC 是数据库层面的协议,TCC 是业务层面的模式。TCC 把控制权交给开发者,避免了 2PC 的长时间锁资源问题。
实战项目:用 Go 实现一个简单的 Saga 模式
我们选择 Saga 模式,因为它最容易理解,且适合初学者。目标:模拟“创建订单 + 扣余额”。
步骤 1:创建项目结构
mkdir saga-demo && cd saga-demo
go mod init saga-demo
步骤 2:定义数据库连接(简化版)
// db.go
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func connectDB(dsn string) *sql.DB {
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
return db
}
步骤 3:实现订单服务
// order_service.go
package main
import "database/sql"
type OrderService struct {
db *sql.DB
}
func (s *OrderService) CreateOrder(userID, amount int) error {
_, err := s.db.Exec("INSERT INTO orders(user_id, amount) VALUES(?, ?)", userID, amount)
return err
}
func (s *OrderService) CompensateCreateOrder(userID int) error {
// 补偿:删除该用户最新订单(简化逻辑)
_, err := s.db.Exec("DELETE FROM orders WHERE user_id = ? ORDER BY id DESC LIMIT 1", userID)
return err
}
步骤 4:实现账户服务
// account_service.go
package main
import "database/sql"
type AccountService struct {
db *sql.DB
}
func (s *AccountService) DeductBalance(userID, amount int) error {
_, err := s.db.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?", amount, userID, amount)
return err
}
func (s *AccountService) CompensateDeductBalance(userID, amount int) error {
// 补偿:加回余额
_, err := s.db.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, userID)
return err
}
步骤 5:编写 Saga 流程控制器
// saga.go
package main
import "fmt"
type Saga struct {
orderSvc *OrderService
accountSvc *AccountService
}
func (s *Saga) Execute(userID, amount int) error {
// Step 1: 创建订单
if err := s.orderSvc.CreateOrder(userID, amount); err != nil {
fmt.Println("创建订单失败,无需补偿")
return err
}
// Step 2: 扣余额
if err := s.accountSvc.DeductBalance(userID, amount); err != nil {
fmt.Println("扣余额失败,开始补偿订单...")
s.orderSvc.CompensateCreateOrder(userID) // 补偿第一步
return err
}
fmt.Println("订单创建 + 扣款成功!")
return nil
}
步骤 6:主函数 & 初始化表
先手动在两个数据库中创建表(可用 docker exec -it order-db mysql -uroot -p123456 进入):
-- order-db
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
CREATE TABLE orders (id INT AUTO_INCREMENT, user_id INT, amount INT, PRIMARY KEY(id));
-- account-db
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
CREATE TABLE accounts (id INT PRIMARY KEY, balance INT);
INSERT INTO accounts VALUES (1, 100); -- 初始余额100
主程序:
// main.go
package main
func main() {
orderDB := connectDB("root:123456@tcp(localhost:3306)/demo")
accountDB := connectDB("root:123456@tcp(localhost:3307)/demo")
saga := &Saga{
orderSvc: &OrderService{db: orderDB},
accountSvc: &AccountService{db: accountDB},
}
// 尝试下单:用户1 购买 50 元商品
err := saga.Execute(1, 50)
if err != nil {
panic(err)
}
}
运行:
go run .
如果一切正常,你会看到:订单创建 + 扣款成功!
你可以故意把 DeductBalance 改成扣 200(超过余额),观察补偿逻辑是否触发。
常见问题解答(新手必看)
Q1:为什么不用 2PC?Go 有现成库吗?
A:2PC 需要数据库支持 XA 协议,配置复杂,且 Go 生态中成熟方案较少(如 go-xa 不活跃)。对初学者,Saga/TCC 更实用。
Q2:补偿操作失败怎么办?
A:这是 Saga 的难点!通常需要:
- 重试机制(指数退避)
- 人工介入通道(如告警 + 后台修复工具)
- 幂等设计(补偿操作可重复执行)
Q3:这代码能上生产吗?
A:不能! 本文仅为教学。生产环境需考虑:
- 事务日志持久化(记录每一步状态)
- 分布式锁(防止并发冲突)
- 监控与告警
- 使用成熟框架(如 Seata、DTM)
学习建议 & 下一步
- 动手改代码:尝试加入“库存服务”,变成三步 Saga。
- 阅读源码:看看开源项目 DTM(Go 写的分布式事务框架)是如何实现的。
- 刷面试题:
- “如何保证补偿操作的幂等性?”
- “TCC 中 Try 阶段为什么要冻结资源?”
- 避坑指南:
- 不要一上来就追求强一致性,先理解业务容忍度
- 分布式事务是“最后手段”,优先考虑通过业务设计避免(如:异步消息 + 本地事务)
分布式事务听起来吓人,但拆解开来,不过是一系列“执行 + 回滚”的逻辑。我当初学的时候,就是从一个简单的补偿函数开始,一步步走到今天。希望这篇实践教程能帮你迈出第一步。
如果你觉得有用,欢迎在评论区留言你的实验结果,或者告诉我你想看哪个方案的深入实现(TCC?Seata 集成?)。技术分享的意义,就是让后来者少走弯路。

评论 0