用python輕鬆完成一個分散式事務TCC,保姆級教程

dongfuye發表於2021-07-17

什麼是分散式事務?銀行跨行轉賬業務是一個典型分散式事務場景,假設A需要跨行轉賬給B,那麼就涉及兩個銀行的資料,無法通過一個資料庫的本地事務保證轉賬的ACID,只能夠通過分散式事務來解決。

分散式事務就是指事務的發起者、資源及資源管理器和事務協調者分別位於分散式系統的不同節點之上。在上述轉賬的業務中,使用者A-100操作和使用者B+100操作不是位於同一個節點上。本質上來說,分散式事務就是為了保證在分散式場景下,資料操作的正確執行。

什麼是TCC分散式事務,TCC是Try、Confirm、Cancel三個詞語的縮寫,最早是由 Pat Helland 於 2007 年發表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。

TCC組成

TCC分為3個階段

  • Try 階段:嘗試執行,完成所有業務檢查(一致性), 預留必須業務資源(準隔離性)
  • Confirm 階段:如果所有分支的Try都成功了,則走到Confirm階段。Confirm真正執行業務,不作任何業務檢查,只使用 Try 階段預留的業務資源
  • Cancel 階段:如果所有分支的Try有一個失敗了,則走到Cancel階段。Cancel釋放 Try 階段預留的業務資源。

TCC分散式事務裡,有3個角色,與經典的XA分散式事務一樣:

  • AP/應用程式,發起全域性事務,定義全域性事務包含哪些事務分支
  • RM/資源管理器,負責分支事務各項資源的管理
  • TM/事務管理器,負責協調全域性事務的正確執行,包括Confirm,Cancel的執行,並處理網路異常

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

TCC網路異常

TCC在整個全域性事務的過程中,可能發生各類網路異常情況,典型的是空回滾、冪等、懸掛,由於TCC的異常情況,和SAGA、可靠訊息等事務模式有相近的地方,因此我們把所有異常的解決方案統統放在這篇文章《還被分散式事務的網路異常困擾嗎?一個函式呼叫幫你搞定它》進行講解

TCC實踐

下面我們進行一個TCC事務的具體開發

目前可用於TCC的開源框架,主要為Java語言,其中以seata為代表。我們的例子採用python,使用的分散式事務框架為dtm,它對分散式事務的支援非常優雅。下面來詳細講解TCC的組成

下面我們來編寫具體的Try/Confirm/Cancel的處理函式

@app.post("/api/TransOutTry")
def trans_out_try():
    return {"result": "SUCCESS"}

@app.post("/api/TransOutConfirm")
def trans_out_confirm():
    return {"result": "SUCCESS"}

@app.post("/api/TransOutCancel")
def trans_out_cancel():
    return {"result": "SUCCESS"}

@app.post("/api/TransInTry")
def trans_in_try():
    return {"result": "SUCCESS"}

@app.post("/api/TransInConfirm")
def trans_in_confirm():
    return {"result": "SUCCESS"}

@app.post("/api/TransInCancel")
def trans_in_cancel():
    return {"result": "SUCCESS"}

到此各個子事務的處理函式已經OK了,然後是開啟TCC事務,進行分支呼叫

# 這是dtm服務地址
dtm = "http://localhost:8080/api/dtmsvr"
# 這是業務微服務地址
svc = "http://localhost:5000/api"

@app.get("/api/fireTcc")
def fire_tcc():
    # 發起tcc事務
    gid = tcc.tcc_global_transaction(dtm, tcc_trans)
    return {"gid": gid}

# tcc事務的具體處理
def tcc_trans(t):
    req = {"amount": 30} # 業務請求的負荷
    # 呼叫轉出服務的Try|Confirm|Cancel
    t.call_branch(req, svc + "/TransOutTry", svc + "/TransOutConfirm", svc + "/TransOutCancel")
    # 呼叫轉入服務的Try|Confirm|Cancel
    t.call_branch(req, svc + "/TransInTry", svc + "/TransInConfirm", svc + "/TransInCancel")

至此,一個完整的TCC分散式事務編寫完成。

如果您想要完整執行一個成功的示例,那麼參考這個例子yedf/dtmcli-node-sample,將它執行起來非常簡單

# 部署啟動dtm
# 需要docker版本18以上
git clone https://github.com/yedf/dtm
cd dtm
docker-compose up

# 另起一個命令列
git clone https://github.com/yedf/dtmcli-py-sample
cd dtmcli-cli-sample
pip3 install flask dtmcli requests
flask run

# 另起一個命令列
curl localhost:5000/api/fireTcc

TCC的回滾

假如銀行將金額準備轉入使用者2時,發現使用者2的賬戶異常,返回失敗,會怎麼樣?我們給出事務失敗互動的時序圖

這個跟成功的TCC差別就在於,當某個子事務返回失敗後,後續就回滾全域性事務,呼叫各個子事務的Cancel操作,保證全域性事務全部回滾。

小結

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

閱讀完此篇乾貨,歡迎大家訪問dtm專案,給顆星星支援!
github.com/yedf/dtm

關於分散式事務中需要處理的冪等、懸掛、空補償,請參考另一篇文章:

分散式事務你不能不知的坑,一個函式呼叫幫你搞定它

如果您使用的是go,您可以訪問dtm ,裡面有go語言執行tcc的例子

如果您使用的是node,您可以訪問yedf/dtmcli-node-sample,裡面有node語言執行tcc的例子

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章