go-zero 分布式事务最佳实践

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: go-zero 分布式事务最佳实践

背景

随着业务的快速发展、业务复杂度越来越高,微服务作为最佳解决方案之一,它解耦服务,降低复杂度,增加可维护性的同时,也带来一部分新问题。

当我们需要跨服务保证数据一致性时,原先的数据库事务力不从心,无法将跨库、跨服务的多个操作放在一个事务中。这样的应用场景非常多,我们可以列举出很多:

  • 跨行转账场景,数据不在一个数据库,但需要保证余额扣减和余额增加要么同时成功,要么同时失败
  • 发布文章后,更新文章总数等统计信息。其中发布文章和更新统计信息通常在不同的微服务中
  • 微服务化之后的订单系统
  • 出行旅游需要在第三方系统同时定几张票

面对这些本地事务无法解决的场景,我们需要分布式事务的解决方案,保证跨服务、跨数据库更新数据的一致性。

go-zero 与 dtm 强强联合,推出了在 go-zero 中无缝接入 dtm 的极简方案,让分布式事务的使用从未如此简单。

运行一个例子

我们来看一个可运行的例子,然后再看如何自己开发完成一个完整的分布式事务

下面以 etcd 作为注册服务中心,可以按照如下步骤运行一个 go-zero 的示例:

  • 配置 dtm
MicroService:
    Driver: 'dtm-driver-gozero' # 配置dtm使用go-zero的微服务协议
    Target: 'etcd://localhost:2379/dtmservice' # 把dtm注册到etcd的这个地址
    EndPoint: 'localhost:36790' # dtm的本地地址
  • 启动 etcd
# 前提:已安装etcd
etcd
  • 启动 dtm
# 前提:已配置好dtm的数据库链接
go run app/main.go dev
  • 运行一个 go-zero 的服务
git clone github.com/yedf/dtmdriver-clients && cd dtmdriver-clients
cd gozero/trans && go run trans.go
  • 用 go-zero 发起一个 dtm 的事务
# 在dtmdriver-clients的目录下
cd gozero/app && go run main.go

当你在 trans 的日志中看到

2021/12/03 15:44:05 transfer out 30 cents from 1
2021/12/03 15:44:05 transfer in 30 cents to 2
2021/12/03 15:44:05 transfer out 30 cents from 1
2021/12/03 15:44:05 transfer out 30 cents from 1

那就是事务正常完成了

开发接入

参考 yedf/dtmdriver-clients 的代码

// 下面这行导入gozero的dtm驱动
import _ "github.com/yedf/dtmdriver-gozero"
// 使用dtm的客户端dtmgrpc之前,需要执行下面这行调用,告知dtmgrpc使用gozero的驱动来如何处理gozero的url
err := dtmdriver.Use("dtm-driver-gozero")
// check err
// dtm已经通过前面的配置,注册到下面这个地址,因此在dtmgrpc中使用该地址
var dtmServer = "etcd://localhost:2379/dtmservice"
// 下面从配置文件中Load配置,然后通过BuildTarget获得业务服务的地址
var c zrpc.RpcClientConf
conf.MustLoad(*configFile, &c)
busiServer, err := c.BuildTarget()
  // 使用dtmgrpc生成一个消息型分布式事务并提交
 gid := dtmgrpc.MustGenGid(dtmServer)
 msg := dtmgrpc.NewMsgGrpc(dtmServer, gid).
    // 事务的第一步为调用trans.TransSvcClient.TransOut
    // 可以从trans.pb.go中找到上述方法对应的Method名称为"/trans.TransSvc/TransOut"
    // dtm需要从dtm服务器调用该方法,所以不走强类型,而是走动态的url: busiServer+"/trans.TransSvc/TransOut"
  Add(busiServer+"/trans.TransSvc/TransOut", &busi.BusiReq{Amount: 30, UserId: 1}).
  Add(busiServer+"/trans.TransSvc/TransIn", &busi.BusiReq{Amount: 30, UserId: 2})
 err := msg.Submit()

整个开发接入的过程很少,前面的注释已经很清晰,就不再赘述了。

注意事项

在开发接入的过程中,去找 *.pb.go 的文件中的 grpc 访问的方法路径时候,一定要找 invoke 的路径

深入理解动态调用

在 go-zero 使用 dtm 的分布式事务时,许多的调用是从 dtm 服务器发起的,例如 TCC 的 Confirm/CancelSAGA/MSG 的所有调用。

dtm无需知道组成分布式事务的相关业务api的强类型,它是动态的调用这些api。

grpc的调用,可以类比于HTTP的POST,其中:

  • c.BuildTarget() 产生的 target 类似于 URL 中的 Host
  • /trans.TransSvc/TransOut 相当于 URL 中的 Path
  • &busi.BusiReq{Amount: 30, UserId: 1} 相当于 Post 中 Body
  • pb.Response 相当于 HTTP 请求的响应

通过下面这部分代码,dtm 就拿到了完整信息,就能够发起完整的调用了

Add(busiServer+"/trans.TransSvc/TransOut", &busi.BusiReq{Amount: 30, UserId: 1})

更加完整的例子

热心的社区同学 Mikael 帮忙写了一个内容更加丰富的例子,结合实际应用和子事务屏障,完整的演示了一个线上实际运行的分布式事务,有兴趣的同学可以参考:

https://github.com/Mikaelemmmm/gozerodtm

其他方式接入

go-zero 的微服务还有非 etcd 的其他方式,我们依次说明他们的接入方式

直连

对于直连这种方式,您只需要在上面 dtmetcd 配置基础上,将 Target 设置为空字符串即可。

直连的情况,不需要将 dtm 注册到注册中心。

K8S

对于 K8S 这种方式,您只需要在上面 dtmetcd 配置基础上,将 Target 设置为空字符串即可。

K8S 中,将服务注册到 K8S 中,是由 deployment.yaml 完成的,应用内部,不需要进行注册。

直播分享预告

go-zero 作者和我(dtm 作者)将在12月22日晚21点,在 Go 夜读,联合做一场《go-zero 的分布式事务实践》的直播分享,将会带来更多更深入的讨论。欢迎大家届时参加。

直播地址为:https://live.bilibili.com/11171965

小结

这一次 go-zerodtm 的合作,在 go 生态中,打造了首个原生支持分布式事务的微服务解决方案,意义重大。

欢迎大家使用 go-zerodtm,使用我们原生的 分布式事务的微服务解决方案,并 star 支持我们!

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
3月前
|
算法 Go
[go 面试] 雪花算法与分布式ID生成
[go 面试] 雪花算法与分布式ID生成
|
3月前
|
存储 调度
分布式锁设计问题之云存储的最佳实践中保障分布式锁的容错能力如何解决
分布式锁设计问题之云存储的最佳实践中保障分布式锁的容错能力如何解决
|
3月前
|
监控 Go API
带你十天轻松搞定 Go 微服务之大结局(分布式事务)
带你十天轻松搞定 Go 微服务之大结局(分布式事务)
|
3月前
|
存储 NoSQL 算法
Go 分布式令牌桶限流 + 兜底保障
Go 分布式令牌桶限流 + 兜底保障
|
3月前
|
缓存 监控 Kubernetes
go-zero解读与最佳实践(上)
go-zero解读与最佳实践(上)
|
3月前
|
程序员 测试技术 Go
用 Go 编写简洁代码的最佳实践
用 Go 编写简洁代码的最佳实践
|
3月前
|
Go API 数据库
[go 面试] 分布式事务框架选择与实践
[go 面试] 分布式事务框架选择与实践
|
3月前
|
存储 NoSQL 测试技术
go最佳实践:如何舒适地编码
go最佳实践:如何舒适地编码
|
3月前
|
存储 缓存 Java
涨姿势啦!Go语言中正则表达式初始化的最佳实践
在Go语言中,正则表达式是处理字符串的强大工具,但其编译过程可能消耗较多性能。本文探讨了正则表达式编译的性能影响因素,包括解析、状态机构建及优化等步骤,并通过示例展示了编译的时间成本。为了优化性能,推荐使用预编译策略,如在包级别初始化正则表达式对象或通过`init`函数进行错误处理。此外,简化正则表达式和分段处理也是有效手段。根据初始化的复杂程度和错误处理需求,开发者可以选择最适合的方法,以提升程序效率与可维护性。
52 0
涨姿势啦!Go语言中正则表达式初始化的最佳实践
|
3月前
|
存储 缓存 安全