分布式事务解决方案:最佳实践(零基础入门)

递归到天亮
2025-12-17 13:33
阅读 1551

大家好,我是一名开源项目维护者,也经常在社区做技术分享。最近很多初学者问我:“分布式事务到底该怎么学?面试题老是问,但书上讲得太抽象。”
我当初学的时候,也是被“两阶段提交”、“TCC”这些词绕得头晕。今天,我就用最实践的方式,带你从零开始搞懂分布式事务——不用高深理论,只写能跑的代码。


什么是分布式事务?为什么需要它?

想象一下:你在一个电商系统下单,要同时扣库存、创建订单、扣用户余额。这三个操作分别在三个不同的数据库或服务中。

  • 如果扣了库存,但订单没创建成功,钱没扣,那库存就白扣了!
  • 如果订单创建了,但余额没扣,商家就亏了!

分布式事务就是保证这些跨服务的操作“要么全成功,要么全失败”。这就是我们常说的 ACID 中的 原子性(Atomicity)


环境准备:5分钟搭好开发环境

我们将用 Go 语言来演示(Go 在微服务和高并发场景中非常流行,也是大厂面试常考语言)。请确保你已安装:

  1. Go 1.20+(官网下载
  2. Docker(用于快速启动本地数据库)
  3. 任意代码编辑器(推荐 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)

学习建议 & 下一步

  1. 动手改代码:尝试加入“库存服务”,变成三步 Saga。
  2. 阅读源码:看看开源项目 DTM(Go 写的分布式事务框架)是如何实现的。
  3. 刷面试题
    • “如何保证补偿操作的幂等性?”
    • “TCC 中 Try 阶段为什么要冻结资源?”
  4. 避坑指南
    • 不要一上来就追求强一致性,先理解业务容忍度
    • 分布式事务是“最后手段”,优先考虑通过业务设计避免(如:异步消息 + 本地事务)

分布式事务听起来吓人,但拆解开来,不过是一系列“执行 + 回滚”的逻辑。我当初学的时候,就是从一个简单的补偿函数开始,一步步走到今天。希望这篇实践教程能帮你迈出第一步。

如果你觉得有用,欢迎在评论区留言你的实验结果,或者告诉我你想看哪个方案的深入实现(TCC?Seata 集成?)。技术分享的意义,就是让后来者少走弯路。

评论 0

最热最新
暂无评论
递归到天亮Lv.1
0
影响力
0
文章
0
粉丝