7

go-zero分布式事务实践

 2 years ago
source link: https://segmentfault.com/a/1190000041144908
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的路径

image.png

image.png

深入理解动态调用

在go-zero使用dtm的分布式事务时,许多的调用是从dtm服务器发起的,例如TCC的Confirm/Cancel,SAGA/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的其他方式,我们依次说明他们的接入方式

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

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

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

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

直播分享预告

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK