如何實現跨Mysql、Redis和Mongo分散式事務? - dongfu
如何組合多個儲存引擎合併組成分散式事務?
Mysql、Redis、Mongo都是非常火爆的儲存,各有各的優勢。在實際應用中,同時使用多個儲存是很常見的,保證跨多個儲存的資料一致性成為一種需求。
本文給出了一個跨多個儲存引擎實現分散式事務的示例:Mysql、Redis 和 Mongo。此示例基於分散式事務框架https://github.com/dtm-labs/dtm,希望能幫助您解決跨微服務的資料一致性問題。
靈活組合多個儲存引擎形成分散式事務的能力是DTM首先提出的,目前還沒有其他分散式事務框架宣告過這樣的能力。
假設使用者現在正在參與促銷活動:他們有餘額,充值話費,促銷活動將贈送商城積分。餘額儲存在Mysql中,賬單儲存在Redis中,商城積分儲存在Mongo中。由於推廣時間有限,存在參與失敗的可能,所以需要回滾支援。
對於上述問題場景,可以使用DTM的Saga事務,下面我們將詳細講解解決方案。
準備資料
第一步是準備資料。為了方便使用者快速上手示例,我們在en.dtm.pub中準備了所有資料,包括 Mysql、Redis 和 Mongo,具體連線使用者名稱和密碼可以在dtm-labs/ dtm 示例。
編寫業務程式碼
先從最熟悉的儲存引擎Mysql的業務程式碼說起:
以下程式碼使用 Golang:其他語言,如 C#、PHP、Java 可以在這裡找到:[DTM SDKs]( https://en.dtm.pub/ref/sdk.html )
func SagaAdjustBalance(db dtmcli.DB, uid int, amount int) error { _, err := dtmimp.DBExec(db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?" , amount, uid) return err } |
該程式碼主要執行使用者在資料庫中餘額的調整。在我們的示例中,這部分程式碼不僅用於 Saga 的正向操作,還用於補償操作,其中只需要傳入一個負數進行補償。
對於 Redis 和 Mongo,業務程式碼處理類似,只是增加或減少相應的餘額。
如何確保冪等性?
對於 Saga 事務模式,當我們在子事務服務中出現臨時故障時,將重試失敗的操作。這種失敗可能發生在子事務提交之前或之後,因此子事務操作需要是冪等的。
DTM 提供輔助表和輔助函式,幫助使用者快速實現冪等性。對於Mysql,它會在業務資料庫中建立一個輔助表barrier,當使用者啟動一個事務調整餘額時,它會先插入Gid到barrier表中。如果存在重複行,則插入失敗,然後跳過平衡調整,保證冪等性。使用輔助函式的程式碼如下:
app.POST(BusiAPI+"/SagaBTransIn", dtmutil.WrapHandler2(func(c *gin.Context) interface{} { return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error { return SagaAdjustBalance (tx, TransInUID, reqFrom(c).Amount, reqFrom(c).TransInResult) }) })) |
Mongo處理冪等性的方式與Mysql類似,不再贅述。
Redis對冪等性的處理與Mysql不同,主要是事務的原理不同。Redis 事務主要通過 Lua 的原子執行來保證。DTM 輔助函式將通過 Lua 指令碼調整平衡。在調整餘額之前,它會Gid在 Redis 中查詢。如果Gid存在則跳過平衡調整;如果沒有,它將記錄Gid並執行平衡調整。用於輔助函式的程式碼如下:
app.POST(BusiAPI+"/SagaRedisTransOut", dtmutil.WrapHandler2(func(c *gin.Context) interface{} { return MustBarrierFromGin(c).RedisCheckAdjustAmount(RedisGet(), GetRedisAccountKey(TransOutUID), -reqFrom(c).Amount , 7*86400) })) |
如何實現補償回滾?
對於 Saga,我們也需要處理補償操作,但補償不是簡單的反向調整,還有很多陷阱需要注意。
- 一方面,補償需要考慮冪等性,因為補償中也存在上小節描述的失敗和重試。
- 另一方面,補償也需要考慮“空補償”,因為 Saga 的前向操作可能會返回失敗,這可能發生在資料調整之前或之後。對於已經提交調整的失敗,我們需要執行反向調整,但是對於沒有提交調整的失敗,我們需要跳過反向調整。
在DTM提供的helper函式中:
- 一方面會根據forward操作插入的Gid判斷補償是否為空補償,
- 另一方面會再次插入Gid+'compensate'來判斷是否補償是重複操作。如果補償操作正常,則對業務進行資料調整;如有空賠或重複賠,則跳過業務上的調整。
Mysql程式碼如下:
app.POST(BusiAPI+"/SagaBTransInCom", dtmutil.WrapHandler2(func(c *gin.Context) interface{} { return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error { return SagaAdjustBalance (tx, TransInUID, -reqFrom(c).Amount, "") }) })) |
Redis 的程式碼如下。
app.POST(BusiAPI+"/SagaRedisTransOutCom", dtmutil.WrapHandler2(func(c *gin.Context) interface{} { return MustBarrierFromGin(c).RedisCheckAdjustAmount(RedisGet(), GetRedisAccountKey(TransOutUID), reqFrom(c).Amount, 7*86400) })) |
補償服務程式碼與前向操作的程式碼幾乎相同,只是金額乘以-1。DTM 輔助函式會自動正確處理冪等性和空值補償。
其他例外
在編寫正向操作和補償操作時,實際上還有一個例外,叫做“暫停”。當超時或重試次數達到配置的限制時,全域性事務將回滾。正常情況是在補償之前進行正向操作,但在“程式暫停”的情況下,可以在正向操作之前進行補償。所以前向操作也需要判斷是否已經執行了補償,如果已經執行,也需要跳過資料調整。
對於 DTM 使用者,這些異常已經得到了優雅和妥善的處理,作為使用者的您只需要按照MustBarrierFromGin(c).Call上面描述的呼叫,根本不需要關心它們。DTM 處理這些異常的原理在這裡詳細描述:異常和子事務障礙
啟動分散式事務
編寫完各個子事務服務後,下面的程式碼程式碼會發起一個 Saga 全域性事務。
saga := dtmcli.NewSaga(dtmutil.DefaultHTTPServer, dtmcli.MustGenGid(dtmutil.DefaultHTTPServer)). Add(busi.Busi+"/SagaBTransOut", busi.Busi+"/SagaBTransOutCom", &busi.TransReq{Amount: 50}). Add(busi.Busi+"/SagaMongoTransIn", busi.Busi+"/SagaMongoTransInCom", &busi.TransReq{Amount: 30}). Add(busi.Busi+"/SagaRedisTransIn", busi.Busi+"/SagaRedisTransOutIn", &busi.TransReq{Amount: 20}) err := saga.Submit() |
在這部分程式碼中,建立了一個Saga全域性事務,由3個子交易組成。
- 從Mysql轉出50個
- 轉入30個到Mongo
- 轉入20個到Redis
在整個交易過程中,如果所有的子交易都成功完成,那麼全域性交易就會成功;如果其中一個子交易返回業務失敗,那麼全域性交易就會回滾。
執行
如果你想執行一個完整的上述例子,步驟如下。
1.、執行DTM
git clone https://github.com/dtm-labs/dtm && cd dtm go run main.go |
2.、執行一個成功的例子
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples go run main.go http_saga_multidb |
3、執行一個失敗的例子
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples go run main.go http_saga_multidb_rollback |
你可以修改這個例子來模擬各種臨時故障、空補償情況以及其他各種例外情況,當整個全域性事務完成後,資料是一致的。
總結
本文給出了一個跨越Mysql、Redis和Mongo的分散式事務的例子。它詳細描述了需要處理的問題,以及解決方案。
本文的原則適用於所有支援ACID事務的儲存引擎,你也可以快速擴充套件到其他引擎,如TiKV。
歡迎訪問github.com/dtm-labs/dtm:它是一個專門的專案,讓微服務中的分散式事務變得更容易。它支援多種語言,以及多種模式,如2段訊息事務、Saga、Tcc和XA。
相關文章
- 跨Mysql、Redis、Mongo的分散式事務MySqlRedisGo分散式
- Golang 實現 Redis(8): TCC分散式事務GolangRedis分散式
- 分散式事務(3)---RocketMQ實現分散式事務原理分散式MQ
- 分散式事務(4)---RocketMQ實現分散式事務專案分散式MQ
- Apache ShardingSphere 如何實現分散式事務Apache分散式
- node.js 中使用redis實現分散式事務鎖Node.jsRedis分散式
- MySQL 中基於 XA 實現的分散式事務MySql分散式
- Redis如何實現分散式鎖Redis分散式
- 分散式事務和分散式hash分散式
- 如何用 Redis 實現分散式鎖Redis分散式
- 如何使用Redis實現分散式鎖Redis分散式
- 如何在Redis中實現事務Redis
- 基於RocketMQ實現分散式事務MQ分散式
- 實戰與原理:如何基於RocketMQ實現分散式事務?MQ分散式
- go-zero微服務實戰系列(十、分散式事務如何實現)Go微服務分散式
- MySQL資料庫分散式事務XA的實現原理分析MySql資料庫分散式
- 分散式事務實戰分散式
- 分散式事務之資料庫事務與JDBC事務實現(一)分散式資料庫JDBC
- 如何用REDIS實現分散式快取Redis分散式快取
- 分散式事務的幾種實現方式分散式
- SpringCloud+RocketMQ實現分散式事務SpringGCCloudMQ分散式
- 使用JOTM實現分散式事務的例子分散式
- 使用Spring Boot實現分散式事務Spring Boot分散式
- php基於dtm分散式事務管理器實現tcc模式分散式事務demoPHP分散式模式
- Redis分散式鎖的原理和實現Redis分散式
- 使用redis和zookeeper實現分散式鎖Redis分散式
- 分散式事務(一)—分散式事務的概念分散式
- 本地事務和分散式事務的區別分散式
- kratos分散式事務實踐分散式
- 分散式鎖----Redis實現分散式Redis
- Redis實現分散式鎖Redis分散式
- Redis分散式實現原理Redis分散式
- 【Redis】利用 Redis 實現分散式鎖Redis分散式
- 資料庫分散式事務的實現原理!資料庫分散式
- 如何實現一個TCC分散式事務框架的一點思考分散式框架
- 用 Redis 實現分散式鎖與實現任務佇列Redis分散式佇列
- 分散式事務之事務實現模式與技術(四)分散式模式
- XA式、非XA式Spring分散式事務的實現Spring分散式