<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用 Go 輕松完成一個 TCC 分布式事務(wù),保姆級教程

          共 4839字,需瀏覽 10分鐘

           ·

          2021-07-27 05:30

          什么是 TCC,TCC 是 Try、Confirm、Cancel 三個詞語的縮寫,最早是由 Pat Helland 于 2007 年發(fā)表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。

          TCC 組成

          TCC 分為 3 個階段

          • Try 階段:嘗試執(zhí)行,完成所有業(yè)務(wù)檢查(一致性), 預留必須業(yè)務(wù)資源(準隔離性)

          • Confirm 階段:如果所有分支的 Try 都成功了,則走到 Confirm 階段。Confirm 真正執(zhí)行業(yè)務(wù),不作任何業(yè)務(wù)檢查,只使用 Try 階段預留的業(yè)務(wù)資源

          • Cancel 階段:如果所有分支的 Try 有一個失敗了,則走到 Cancel 階段。Cancel 釋放 Try 階段預留的業(yè)務(wù)資源。

          TCC 分布式事務(wù)里,有 3 個角色,與經(jīng)典的 XA 分布式事務(wù)一樣:

          • AP/應(yīng)用程序,發(fā)起全局事務(wù),定義全局事務(wù)包含哪些事務(wù)分支

          • RM/資源管理器,負責分支事務(wù)各項資源的管理

          • TM/事務(wù)管理器,負責協(xié)調(diào)全局事務(wù)的正確執(zhí)行,包括 Confirm,Cancel 的執(zhí)行,并處理網(wǎng)絡(luò)異常

          如果我們要進行一個類似于銀行跨行轉(zhuǎn)賬的業(yè)務(wù),轉(zhuǎn)出(TransOut)和轉(zhuǎn)入(TransIn)分別在不同的微服務(wù)里,一個成功完成的 TCC 事務(wù)典型的時序圖如下:


          TCC 網(wǎng)絡(luò)異常

          TCC 在整個全局事務(wù)的過程中,可能發(fā)生各類網(wǎng)絡(luò)異常情況,典型的是空回滾、冪等、懸掛,由于 TCC 的異常情況,和 SAGA、可靠消息等事務(wù)模式有相近的地方,因此我們把所有異常的解決方案統(tǒng)統(tǒng)放在這篇文章《還被分布式事務(wù)的網(wǎng)絡(luò)異常困擾嗎?一個函數(shù)調(diào)用幫你搞定它》進行講解

          TCC 實踐

          對于前面的跨行轉(zhuǎn)賬操作,最簡單的做法是,在 Try 階段調(diào)整余額,在 Cancel 階段反向調(diào)整余額,Confirm 階段則空操作。這么做帶來的問題是,如果 A 扣款成功,金額轉(zhuǎn)入 B 失敗,最后回滾,把 A 的余額調(diào)整為初始值。在這個過程中如果 A 發(fā)現(xiàn)自己的余額被扣減了,但是收款方 B 遲遲沒有收到余額,那么會對 A 造成困擾。

          更好的做法是,Try 階段凍結(jié) A 轉(zhuǎn)賬的金額,Confirm 進行實際的扣款,Cancel 進行資金解凍,這樣用戶在任何一個階段,看到的數(shù)據(jù)都是清晰明了的。

          下面我們進行一個 TCC 事務(wù)的具體開發(fā)

          目前可用于 TCC 的開源框架,主要為 Java 語言,其中以 seata 為代表。我們的例子采用 go 語言,使用的分布式事務(wù)框架為dtm,它對分布式事務(wù)的支持非常優(yōu)雅。下面來詳細講解 TCC 的組成

          我們首先創(chuàng)建兩張表,一張是用戶余額表,一張是凍結(jié)資金表,建表語句如下:

          CREATE TABLE dtm_busi.`user_account` (
          `id` int(11) AUTO_INCREMENT PRIMARY KEY,
          `user_id` int(11) not NULL UNIQUE ,
          `balance` decimal(10,2) NOT NULL DEFAULT '0.00',
          `create_time` datetime DEFAULT now(),
          `update_time` datetime DEFAULT now()
          );

          CREATE TABLE dtm_busi.`user_account_trading` (
          `id` int(11) AUTO_INCREMENT PRIMARY KEY,
          `user_id` int(11) not NULL UNIQUE ,
          `trading_balance` decimal(10,2) NOT NULL DEFAULT '0.00',
          `create_time` datetime DEFAULT now(),
          `update_time` datetime DEFAULT now()
          );

          trading 表中,trading_balance 記錄正在交易的金額。

          我們先編寫核心代碼,凍結(jié)/解凍資金操作,會檢查約束 balance+trading_balance >= 0,如果約束不成立,執(zhí)行失敗

          func adjustTrading(uid int, amount int) (interface{}, error) {
          冪等、懸掛處理
          dbr := sdb.Exec("update dtm_busi.user_account_trading t join dtm_busi.user_account a on t.user_id=a.user_id and t.user_id=? set t.trading_balance=t.trading_balance + ? where a.balance + t.trading_balance + ? >= 0", uid, amount, amount)
          if dbr.Error == nil && dbr.RowsAffected == 0 { // 如果余額不足,返回錯誤
          return nil, fmt.Errorf("update error, balance not enough")
          }
          其他情況檢查及處理
          }

          然后是調(diào)整余額

          func adjustBalance(uid int, amount int) (ret interface{}, rerr error) {
          冪等、懸掛處理
          這里略去進行相關(guān)的事務(wù)處理,包括開啟事務(wù),以及在defer中處理提交或回滾
          // 將原先凍結(jié)的資金記錄解凍
          dbr := db.Exec("update dtm_busi.user_account_trading t join dtm_busi.user_account a on t.user_id=a.user_id and t.user_id=? set t.trading_balance=t.trading_balance + ?", uid, -amount)
          if dbr.Error == nil && dbr.RowsAffected == 1 { // 解凍成功
          // 調(diào)整金額
          dbr = db.Exec("update dtm_busi.user_account set balance=balance+? where user_id=?", amount, uid)
          }
          其他情況檢查及處理
          }

          下面我們來編寫具體的 Try/Confirm/Cancel 的處理函數(shù)

          RegisterPost(app, "/api/TransInTry", func (c *gin.Context) (interface{}, error) {
          return adjustTrading(1, reqFrom(c).Amount)
          })
          RegisterPost(app, "/api/TransInConfirm", func TransInConfirm(c *gin.Context) (interface{}, error) {
          return adjustBalance(1, reqFrom(c).Amount)
          })
          RegisterPost(app, "/api/TransInCancel", func TransInCancel(c *gin.Context) (interface{}, error) {
          return adjustTrading(1, -reqFrom(c).Amount)
          })

          RegisterPost(app, "/api/TransOutTry", func TransOutTry(c *gin.Context) (interface{}, error) {
          return adjustTrading(2, -reqFrom(c).Amount)
          })
          RegisterPost(app, "/api/TransOutConfirm", func TransInConfirm(c *gin.Context) (interface{}, error) {
          return adjustBalance(2, -reqFrom(c).Amount)
          })
          RegisterPost(app, "/api/TransOutCancel", func TransInCancel(c *gin.Context) (interface{}, error) {
          return adjustTrading(2, reqFrom(c).Amount)
          })

          到此各個子事務(wù)的處理函數(shù)已經(jīng) OK 了,然后是開啟 TCC 事務(wù),進行分支調(diào)用

          // TccGlobalTransaction 會開啟一個全局事務(wù)
          _, err := dtmcli.TccGlobalTransaction(DtmServer, func(tcc *dtmcli.Tcc) (rerr error) {
          // CallBranch 會將事務(wù)分支的Confirm/Cancel注冊到全局事務(wù)上,然后直接調(diào)用Try
          res1, rerr := tcc.CallBranch(&TransReq{Amount: 30}, host+"/api/TransOutTry", host+"/api/TransOutConfirm", host+"/api/TransOutRevert"
          進行錯誤檢查,以及其他邏輯
          res2, rerr := tcc.CallBranch(&TransReq{Amount: 30}, host+"/api/TransInTry", host+"/api/TransInConfirm", host+"/api/TransInRevert")
          進行錯誤檢查,有任何錯誤,返回錯誤,回滾交易
          // 如果沒有錯誤,函數(shù)正常返回后,全局事務(wù)會提交,TM會調(diào)用各個事務(wù)分支的Confirm,完成整個事務(wù)
          })

          至此,一個完整的 TCC 分布式事務(wù)編寫完成。

          如果您想要完整運行一個成功的示例,那么按照 dtm 項目的說明搭建好環(huán)境之后,運行下面命令運行 tcc 的例子即可

          go run app/main.go tcc_barrier

          TCC 的回滾

          假如銀行將金額準備轉(zhuǎn)入用戶 2 時,發(fā)現(xiàn)用戶 2 的賬戶異常,返回失敗,會怎么樣?我們給出事務(wù)失敗交互的時序圖


          這個跟成功的 TCC 差別就在于,當某個子事務(wù)返回失敗后,后續(xù)就回滾全局事務(wù),調(diào)用各個子事務(wù)的 Cancel 操作,保證全局事務(wù)全部回滾。

          小結(jié)

          在這篇文章里,我們介紹了 TCC 的理論知識,也通過一個例子,完整給出了編寫一個 TCC 事務(wù)的過程,涵蓋了正常成功完成,以及成功回滾的情況。相信讀者通過這邊文章,對 TCC 已經(jīng)有了深入的理解。

          關(guān)于分布式事務(wù)中需要處理的冪等、懸掛、空補償,請參考另一篇文章:

          分布式事務(wù)你不能不知的坑,一個函數(shù)調(diào)用幫你搞定它

          文中使用的例子節(jié)選自dtm,它由 go 實現(xiàn),支持多種事務(wù)模式:TCC、SAGA、XA、事務(wù)消息 跨語言支持,已支持 golang、python、PHP、nodejs 等語言的客戶端。提供子事務(wù)屏障功能,優(yōu)雅的解決了冪等、懸掛、空補償?shù)葐栴}。

          閱讀完此篇干貨,歡迎大家訪問dtm項目,給顆星星支持!

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一级黄色A片 | 五月丁香av小说网 | 亚洲欧洲免费 | 国产成人精品一区二区 | 青娱乐国产一区二区三区 |