Distributed transaction

Distributed Transaction 特指多個服務同時訪問多個數據源的事務處理機制,請注意它與DTP(Ddistributed Transaction Processing, 分布式事務處理) 模型中“分佈式事務”的差異。DTP 模型所指的“分佈式”是相對於數據源而言的,並不涉及服務 此 Distributed Transaction 是相對於服務而言的,如果嚴謹地說,它更應該被稱為“在分佈式服務環境下的事務處理機制”。

可靠事件對列 (Reliable Event Queues)

可靠事件對列是一種基於消息隊列的分布式事務管理方式,主要目的是實現異步處理的事務一致性。它通常是將事務操作轉換成消息,然後放入消息隊列中,再由後續的消費者進行處理,實現整個分布式事務的一致性。可靠事件對列通常會提供容錯機制,比如消息重試、消息死信等,可以保證即使發生故障,也能夠保持事務的可靠性。

  • 容易出錯的最先進行 : 即:賬號扣款→ 倉庫出庫→ 商家收款
  • 通常的設計是讓消息帶上一個唯一的事務ID,以保證一個事務中的出庫、收款動作會且只會被處理一次。

TCC(try confirm cancel) 分布式事務

TCC 是一種常見的分布式事務機制, 是由數據庫專家 Pat Helland在 2007年撰寫的論文 "Life beyond Distributed Transcations: an Apostate's Opinion" 中提出的一種分布式事務解決方案, 這種解決方案的核心思想是: 將一個大事務拆分成三個子事務, 分別為 Try、Confirm、Cancel, 這三個子事務分別對應著一個獨立的數據庫操作, 並且每個子事務都有獨立的回滾機制, 這樣就可以保證整個事務的最終一致性.

通常是在微服務架構中使用。Try階段進行業務檢查、鎖定資源等操作,Confirm階段提交事務,Cancel階段回滾事務。TCC要求應用開發人員手動實現協調邏輯,比較複雜,但是它可以保證事務的原子性,如果某個子事務失敗,可以回滾整個事務。

與可靠消息隊列相比, TCC 的優點是可以保證事務的最終一致性, 但是它的缺點是實現起來比較複雜, 需要開發人員自己實現 Try、Confirm、Cancel 三個子事務, 並且需要自己實現回滾機制. 可靠消息隊列沒有任何隔離性可言.

缺乏隔離性帶來一個明顯的問題就是超售: 如兩個客戶同時購買了同一件商品, 但是庫存只有一件, 這時候就會出現超售的情況. 這種情況在電商平台上是非常常見的, 例如雙十一的時候, 一些商品的銷量可能會超過庫存的數量, 這時候就會出現超售的情況. 這場景就需要可重複讀(Repeatable Read)的隔離級別來解決. 以確保後面提交的事務會因為無法獲得鎖而導致失敗.

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

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

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

使用 Mutex 實現 TCC

import (
    "fmt"
    "sync"
)

var (
    mutex sync.Mutex
    balance = 100 // 商品庫存
)

// TCC try 操作
func try(amount int) bool {
    mutex.Lock()
    defer mutex.Unlock()
    if balance < amount {
        return false
    }
    balance -= amount
    return true
}

// TCC confirm 操作
func confirm(amount int) {
    mutex.Lock()
    defer mutex.Unlock()
    balance += amount
}

// TCC cancel 操作
func cancel(amount int) {
    mutex.Lock()
    defer mutex.Unlock()
    balance += amount
}

func main() {
    // 假設需要扣除 50 個商品
    amount := 50
    if try(amount) {
        // 如果 try 操作成功,則進行 confirm 操作
        confirm(amount)
        fmt.Println("扣除商品成功")
    } else {
        // 如果 try 操作失敗,則進行 cancel 操作
        cancel(amount)
        fmt.Println("扣除商品失敗")
    }
}

使用 Redis 實現 TCC

func try(redis *redis.Client, key string, amount int) bool {
    tx := redis.TxPipeline()
    tx.Watch(key) // 在 Redis 中,Watch 命令用於監視指定的鍵,並在事務開始前監視這些鍵。如果在事務開始後,任何被監視的鍵被修改,則事務將失敗
    if val, _ := tx.Get(key).Int(); val < amount {
        tx.Discard()
        return false
    }
    tx.DecrBy(key, amount)
    tx.Exec()
    return true
}

func confirm(redis *redis.Client, key string, amount int) {
    tx := redis.TxPipeline()
    tx.IncrBy(key, amount)
    tx.Exec()
}

func cancel(redis *redis.Client, key string, amount int) {
    tx := redis.TxPipeline()
    tx.IncrBy(key, amount)
    tx.Exec()
}

func main() {
    redis := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })
    defer redis.Close()

    // 設定商品庫存
    key := "item:1001:stock"
    redis.Set(key, 10, 0)

    // 嘗試扣除商品庫存
    amount := 5
    if try(redis, key, amount) {
        // 扣除成功,執行 Confirm
        confirm(redis, key, amount)
        fmt.Println("Order placed successfully.")
    } else {
        // 扣除失敗,執行 Cancel
        cancel(redis, key, amount)
        fmt.Println("Order failed due to insufficient stock.")
    }
}
  1. 其中,try 函數為 TCC 的「試」階段,用於嘗試執行一個操作。 首先在 Redis 上使用 WATCH 命令監視一個鍵, Watch 命令用於監視指定的鍵,並在事務開始前監視這些鍵。 如果在事務開始後,任何被監視的鍵被修改,則事務將失敗 接著使用 GET 命令獲取鍵的值,判斷庫存是否充足。如果庫存充足,使用 DECRBY 命令減少庫存,然後執行 Redis 事務。
  2. confirm 函數為 TCC 的「確認」階段,用於確認執行一個操作。它使用 INCRBY 命令增加鍵的值,恢復之前減少的庫存。
  3. cancel 函數為 TCC 的「取消」階段,用於取消執行一個操作。它也使用 INCRBY 命令增加鍵的值,恢復之前減少的庫存。

在主函數中,我們可以使用 try 函數進行庫存檢查。如果庫存不足,就不能執行該操作,否則就執行 TCC 的「確認」階段,並最終執行 TCC 的「取消」階段。

Saga

Saga是另一種分布式事務管理協議,通常使用狀態機實現。它將一個複雜的分布式事務拆分為多個子事務,每個子事務都是一個本地事務,而且都是可以回滾的,即使某個子事務失敗,也可以將之前已經執行的事務回滾。Saga通常會維護一個狀態機,用於記錄事務的執行狀態,並且通過狀態機的更新實現整個分布式事務的一致性。Saga的優點是可以容錯、重試等,而且在處理複雜的分布式事務時比較方便,但是在大規模的應用中,狀態機的管理可能會變得很複雜。而且Saga要求開發人員手動實現事務的回滾邏輯,如果不留意可能會引入新的問題。

比較

分佈式事務AT、TCC、Saga、XA 模式分析對比

分布式事務模式 介绍 技術實現
AT 模式 無侵入的分布式事務解決方案,適用於不希望對業務進行改造的場景,幾乎0學習成本(sql都由框架托管统一執行,會存在髒寫問題) seata、shardingsphere
TCC 模式 高性能分布式事務解決方案,適用於核心系统等對性能有很高要求的場景(第一階段會產生行鎖,事務執行太久會鎖行很久) seata、service-comb
Saga 模式 長事務解決方案,適用於業務流程長且需要保證事務最终一致性的業務系统(第一階段就操作DB,會存在髒讀問題) seata、shardingsphere、service-comb
XA模式 分布式强一致性的解决方案,但性能低而使用較少。 seata、shardingsphere

Saga和TCC模式区别不大,TCC就是多了个鎖行的步骤(避免了髒讀,但事務執行太久會導致鎖行很久,不適用於長事務)

AT vs TCC

AT TCC
是否需要開發者解決懸掛和空回滾問題 不需要 需要
性能 低(需要全局鎖導致) 高(無鎖)
回滾日誌 需要 不需要
全局鎖 需要 不需要
commit/cancel階段代碼實現 不需要 需要

作者:DH大黃 鏈接:https://www.jianshu.com/p/4e26eec34b46 來源:簡書 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

可靠事件對列, TCC事務, Saga

可靠事件對列(Reliable Event Queues)通常使用消息隊列作為基礎,可以通過將事務操作轉化為消息,保證異步處理的事務一致性。可靠事件對列具有較好的可擴展性,能夠快速處理大量的事務操作,並且可以容錯、重試等,提高事務處理的可靠性。

TCC(Try-Confirm-Cancel)是一種分布式事務協議,通過分階段提交和回滾來保證分布式事務的一致性。TCC事務需要開發人員手動實現事務協調邏輯,雖然可靠性較高,但是實現和維護成本也比較高。

Saga是一種分布式事務協議,通過維護一個狀態機,將一個事務拆分為多個子事務,每個子事務都是一個本地事務,通過更新狀態機實現整個事務的一致性。Saga具有較好的可擴展性,能夠輕鬆處理大量的事務操作,同時也能夠容錯、重試等,提高事務處理的可靠性。

可靠事件對列和Saga較為常見,因為它們具有較好的可擴展性和可靠性,能夠更好地應對大量事務操作。TCC事務較少使用,因為實現成本相對較高。

參考資料

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""