Mysql、Redis、Mongoはすべて非常に人気のあるストアであり、それぞれに独自の利点があります。実際のアプリケーションでは、同時に複数のストアを使用するのが一般的であり、複数のストア間でデータの一貫性を確保することが要件になります。
この記事では、複数のストアエンジン、Mysql、Redis、Mongoに分散トランザクションを実装する例を示します。この例は、分散トランザクションフレームワークhttps://github.com/dtm-labs/dtmに基づいており、マイクロサービス全体のデータ整合性の問題を解決するのに役立つことを願っています。
複数のストレージエンジンを柔軟に組み合わせて分散トランザクションを形成する機能は、最初にDTMによって提案されましたが、他の分散トランザクションフレームワークではこのような機能については言及されていません。
問題のシナリオ
最初に問題のシナリオを見てみましょう。ユーザーがプロモーションに参加しているとします。ユーザーには残高があり、電話料金を請求すると、プロモーションによってモールポイントが付与されます。残高はMysqlに保存され、請求書はRedisに保存され、モールポイントはMongoに保存されます。プロモーション期間に限りがあるため、参加に失敗する可能性があり、ロールバックサポートが必要です。
上記の問題シナリオでは、DTMのSagaトランザクションを使用できます。その解決策については、以下で詳しく説明します。
データの準備
最初のステップは、データを準備することです。ユーザーが例をすぐに開始できるように、関連するデータをen.dtm.pubに用意しました。これには、Mysql、Redis、Mongoが含まれます。特定の接続のユーザー名とパスワードは、https://にあります。 github.com/dtm-labs/dtm-examples。
自分でデータ環境をローカルで準備する場合は、https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.ymlを使用してMysql、Redis、Mongoを起動できます。次に、https://github.com/dtm-labs/dtm/tree/main/sqlsでスクリプトを実行して、この例のデータを準備します。ここで、
busi.*
はビジネスデータであり、barrier.*
DTMで使用される補助テーブルです
ビジネスコードの記述
最もよく知られているMysqlのビジネスコードから始めましょう。
次のコードはGolangにあります。 C#、PHP、Javaなどの他の言語はここにあります:DTM SDK
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
}
このコードは、主にデータベース内のユーザーのバランスの調整を実行します。この例では、コードのこの部分は、佐賀の順方向操作だけでなく、補償のために負の金額のみを渡す必要がある補償操作にも使用されます。
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)
}))
補償の方法
佐賀県についても補償業務に取り組む必要がありますが、補償は単なる逆調整ではなく、注意すべき落とし穴がたくさんあります。
一方では、前のサブセクションで説明した失敗と再試行も補償に存在するため、補償ではべき等性を考慮する必要があります。一方、佐賀の順方向操作では、データ調整の前後に発生した可能性のある障害が返される可能性があるため、補正にも「ヌル補正」を考慮する必要があります。調整がコミットされた障害の場合、逆調整を実行する必要があります。ただし、調整がコミットされていない障害の場合は、逆の操作をスキップする必要があります。
DTMが提供するヘルパーテーブルとヘルパー関数では、一方では、順方向操作によって挿入されたGidに基づいて、補正がnull補正であるかどうかを判断し、他方では、Gid+「補正」を再度挿入します。補正が重複操作であるかどうかを判断します。通常の報酬操作がある場合は、ビジネスのデータ調整を実行します。ヌルの報酬または重複した報酬がある場合、ビジネスの調整をスキップします。
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()
コードのこの部分では、3つのサブトランザクションで構成されるSagaグローバルトランザクションが作成されます。
- Mysqlから50を転送する
- 30でMongoに転送
- 20でRedisに転送
トランザクション全体を通じて、すべてのサブトランザクションが正常に完了すると、グローバルトランザクションは成功します。サブトランザクションの1つがビジネスの失敗を返す場合、グローバルトランザクションはロールバックします。
実行
上記の完全な例を実行する場合の手順は次のとおりです。
- DTMを実行する
git clone https://github.com/dtm-labs/dtm && cd dtm
go run main.go
- 成功例を実行する
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb
- 失敗した例を実行する
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb_rollback
例を変更して、さまざまな一時的な障害、null補正の状況、およびグローバルトランザクション全体が終了したときにデータが一貫しているその他のさまざまな例外をシミュレートできます。
まとめ
この記事では、Mysql、Redis、Mongoに分散トランザクションの例を示します。対処する必要のある問題とその解決策について詳しく説明しています。
この記事の原則は、ACIDトランザクションをサポートするすべてのストレージエンジンに適しており、TiKVなどの他のエンジンにもすばやく拡張できます。
github.com/dtm-labs/dtmにアクセスすることを歓迎します。これは、マイクロサービスでの分散トランザクションを容易にするための専用プロジェクトです。複数の言語、および2フェーズメッセージ、Saga、Tcc、Xaなどの複数のパターンをサポートします。