6

微服务中的分布式事务实践

 2 years ago
source link: https://my.oschina.net/kevwan/blog/5375157
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

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

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

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 这种方式,您只需要在上面 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 支持我们!

微信交流群

关注『微服务实践』公众号并点击 交流群 获取社区群二维码。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK