go-zero分散式事務實踐

葉東富發表於2021-12-20

背景

隨著業務的快速發展、業務複雜度越來越高,微服務作為最佳解決方案之一,它解耦服務,降低複雜度,增加可維護性的同時,也帶來一部分新問題。

當我們需要跨服務保證資料一致性時,原先的資料庫事務力不從心,無法將跨庫、跨服務的多個操作放在一個事務中。這樣的應用場景非常多,我們可以列舉出很多:

  • 跨行轉賬場景,資料不在一個資料庫,但需要保證餘額扣減和餘額增加要麼同時成功,要麼同時失敗
  • 釋出文章後,更新文章總數等統計資訊。其中釋出文章和更新統計資訊通常在不同的微服務中
  • 微服務化之後的訂單系統
  • 出行旅遊需要在第三方系統同時定幾張票

面對這些本地事務無法解決的場景,我們需要分散式事務的解決方案,保證跨服務、跨資料庫更新資料的一致性。

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

對於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支援我們

相關文章