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.")
}
}
- 其中,try 函數為 TCC 的「試」階段,用於嘗試執行一個操作。 首先在 Redis 上使用 WATCH 命令監視一個鍵, Watch 命令用於監視指定的鍵,並在事務開始前監視這些鍵。 如果在事務開始後,任何被監視的鍵被修改,則事務將失敗 接著使用 GET 命令獲取鍵的值,判斷庫存是否充足。如果庫存充足,使用 DECRBY 命令減少庫存,然後執行 Redis 事務。
- confirm 函數為 TCC 的「確認」階段,用於確認執行一個操作。它使用 INCRBY 命令增加鍵的值,恢復之前減少的庫存。
- 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事務較少使用,因為實現成本相對較高。