分布式事务太难?Go后端开发者的实战避坑指南
大家好,我是开源项目 go-dtm 的维护者,也是一位常年和分布式系统打交道的后端工程师。我当初学分布式事务时,被“最终一致性”、“两阶段提交”这些术语绕得晕头转向,写出来的代码不是数据不一致,就是服务死锁。今天,我就用最直白的语言,手把手带你用 Go 语言实现一个可靠的分布式事务方案——不讲空理论,只讲最佳实践。
为什么你需要关心分布式事务?
在单体应用中,数据库事务(比如 BEGIN...COMMIT)能保证操作要么全成功,要么全失败。但当你把系统拆成多个微服务(比如用户服务、订单服务、库存服务),每个服务都有自己的数据库,这时候跨服务的操作就无法用本地事务保证一致性了。
举个例子:
用户下单时,要同时扣减库存、创建订单、扣款。如果扣库存成功,但创建订单失败,就会导致“钱没扣,货也没了”——这就是典型的数据不一致问题。
分布式事务,就是为了解决这类问题而生的。
环境准备:5分钟搭建开发环境
我们用 Go + Docker 快速搭建实验环境。
1. 安装依赖
- Go 1.20+(官网下载)
- Docker(用于运行事务协调器)
2. 启动事务协调器 DTM
DTM 是一个开源的分布式事务管理器,支持多种模式,且对 Go 友好。
# 启动 DTM 服务(含 MySQL)
docker run -d --name dtm \
-p 36789:36789 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
ghcr.io/dtm-labs/dtm:latest
3. 初始化项目
mkdir dtm-demo && cd dtm-demo
go mod init dtm-demo
go get github.com/dtm-labs/dtm@latest
go get github.com/go-sql-driver/mysql
💡 提示:DTM 自带 MySQL,无需额外安装数据库。默认账号
root/123456,端口3306。
核心概念:3种主流方案怎么选?
分布式事务没有“银弹”,但有最适合业务场景的方案。以下是三种最常用的方式:
| 方案 | 适用场景 | 一致性 | 性能 | 复杂度 |
|---|---|---|---|---|
| TCC | 高并发、强一致性要求(如金融) | 强一致 | 高 | 高 |
| Saga | 长流程、可补偿(如电商下单) | 最终一致 | 中 | 中 |
| 消息队列 | 异步解耦、容忍延迟(如发通知) | 最终一致 | 高 | 低 |
我的建议:
- 新手从 Saga 开始:逻辑清晰,易于理解。
- 不要一上来就搞 TCC:除非你真的需要强一致。
📌 什么是 Saga?
它把一个大事务拆成多个本地事务,每个步骤都有对应的“补偿操作”(比如“扣库存”对应“回滚库存”)。如果某步失败,就依次执行前面所有步骤的补偿。
实战:用 Go 实现一个订单 Saga
我们来做一个简化版的“下单”流程:
- 扣减库存
- 创建订单
如果第2步失败,就回滚库存。
第一步:定义服务接口
创建 main.go:
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
_ "github.com/go-sql-driver/mysql"
"github.com/dtm-labs/dtm/client/dtmcli"
"github.com/dtm-labs/dtm/client/dtmgrpc/dtmgimp"
"github.com/gin-gonic/gin"
)
var db *sql.DB
func initDB() {
var err error
db, err = sql.Open("mysql", "root:123456@tcp(localhost:3306)/dtm?charset=utf8")
if err != nil {
log.Fatal(err)
}
}
func main() {
initDB()
r := gin.Default()
// 库存服务
r.POST("/stock/reduce", reduceStock)
r.POST("/stock/revert", revertStock)
// 订单服务
r.POST("/order/create", createOrder)
r.POST("/order/compensate", compensateOrder)
r.Run(":8081")
}
第二步:实现业务逻辑
扣减库存
func reduceStock(c *gin.Context) {
// 模拟库存不足
_, err := db.Exec("UPDATE stock SET count = count - 1 WHERE item_id = 1 AND count > 0")
if err != nil {
c.JSON(400, gin.H{"error": "库存不足"})
return
}
c.JSON(200, gin.H{"msg": "success"})
}
回滚库存(补偿操作)
func revertStock(c *gin.Context) {
_, err := db.Exec("UPDATE stock SET count = count + 1 WHERE item_id = 1")
if err != nil {
log.Printf("revert stock failed: %v", err)
// 补偿失败需人工介入,此处先忽略
}
c.JSON(200, gin.H{"msg": "success"})
}
创建订单
func createOrder(c *gin.Context) {
_, err := db.Exec("INSERT INTO orders (user_id, item_id) VALUES (1, 1)")
if err != nil {
c.JSON(400, gin.H{"error": "创建订单失败"})
return
}
c.JSON(200, gin.H{"msg": "success"})
}
⚠️ 注意:补偿操作必须幂等!多次执行结果不变。
第三步:发起 Saga 事务
新建一个客户端 client.go:
package main
import (
"fmt"
"log"
"github.com/dtm-labs/dtm/client/dtmcli"
"github.com/dtm-labs/dtm/client/dtmgrpc/dtmgimp"
)
func main() {
dtmcli.SetLogger(dtmcli.Logf)
// 构建 Saga 事务
saga := dtmcli.NewSaga("http://localhost:36789/api/dtmsvr", dtmcli.MustGenGid()).
Add("http://localhost:8081/stock/reduce", "http://localhost:8081/stock/revert").
Add("http://localhost:8081/order/create", "http://localhost:8081/order/compensate")
// 提交事务
err := saga.Submit()
if err != nil {
log.Fatal("saga failed:", err)
}
fmt.Println("下单成功!")
}
第四步:初始化数据库
连接 DTM 内置的 MySQL:
mysql -h localhost -u root -p123456 dtm
执行建表语句:
CREATE TABLE stock (item_id INT, count INT);
INSERT INTO stock VALUES (1, 10);
CREATE TABLE orders (id INT AUTO_INCREMENT, user_id INT, item_id INT, PRIMARY KEY(id));
第五步:运行测试
- 启动服务:
go run main.go - 模拟正常流程:
go run client.go→ 成功 - 修改
createOrder函数,让它返回错误:c.JSON(400, gin.H{"error": "故意失败"}) - 再次运行
client.go→ 观察日志,会自动调用revertStock!
新手常见问题 & 解决方案
Q1:补偿操作失败怎么办?
A:补偿操作必须设计为可重试且幂等。DTM 会自动重试补偿,但如果多次失败,需人工介入。建议记录失败日志并告警。
Q2:如何保证接口幂等?
A:给每个请求加唯一 ID(如 dtm.trans_gid),在数据库中记录已处理的请求 ID,重复请求直接返回成功。
Q3:Saga 会导致中间状态可见吗?
A:会!比如扣完库存但还没创建订单时,用户可能看到“库存已减但订单未生成”。这是最终一致性的代价,可通过前端提示“处理中”缓解。
Q4:DTM 和 Seata 有什么区别?
A:Seata 更偏向 Java 生态,DTM 对 Go 友好,且协议更简单。如果你用 Go,选 DTM 更省心。
下一步学习建议
- 深入理解 Saga 模式:阅读 DTM 官方文档
- 尝试 TCC 模式:当业务需要强一致时(如转账),TCC 更合适
- 集成到真实项目:把 DTM 接入你的微服务,从小功能开始
- 监控与告警:用 Prometheus + Grafana 监控 DTM 事务状态
🚫 避坑指南:
- 不要为了“技术先进”而强行上分布式事务,先评估业务是否真的需要
- 本地事务能解决的,就别用分布式事务
- 补偿逻辑一定要经过充分测试!
结语
分布式事务听起来高大上,但本质是“分步执行 + 失败回滚”的工程实践。作为 Go 后端开发者,你不需要成为理论专家,只要掌握一种可靠方案(比如 Saga),就能应对 80% 的业务场景。
我当初也是从一个简单的 Demo 开始,踩过无数坑,才慢慢建立起对分布式系统的信心。希望这篇教程能帮你少走弯路。如果你觉得有用,欢迎给 dtm 点个 Star,也欢迎在评论区交流你的实践心得!

评论 0