分散トランザクションのための 6 つのソリューション。非常によく書かれています。

分散トランザクションのための 6 つのソリューション。非常によく書かれています。

[[407285]]

導入

分散システムやマイクロサービス アーキテクチャが普及している今日の世界では、サービス間の呼び出しの失敗は当たり前のことになっています。例外をどのように処理するか、データの一貫性をどのように確保するかは、マイクロサービスの設計プロセスでは避けられない難しい問題になっています。

ビジネスシナリオが異なれば、ソリューションも異なります。一般的な方法は次のとおりです。

  1. 再試行をブロックします。
  2. 2PC、3PC の従来のトランザクション。
  3. キューとバックグラウンドの非同期処理を使用します。
  4. TCC の報酬に関する事項;
  5. ローカル メッセージ テーブル (非同期保証)
  6. MQ トランザクション。

この記事では、他のいくつかの項目に焦点を当てています。 2PC および 3PC の従来の取引については、すでにオンラインで多くの情報が公開されているため、ここでは繰り返しません。

再試行をブロックする

マイクロサービス アーキテクチャでは、再試行をブロックすることが一般的な方法です。

疑似コードの例:

  1. m : = db .Insert(sql) です。  
  2. err : =リクエスト(B-サービス、m)  
  3. func request(url 文字列,body インターフェース{}){  
  4. i: = 0の場合; i < 3 ;私は++{  
  5. 結果、エラー=リクエスト.POST(url,body)  
  6. エラー== nilの場合{  
  7. 壊す 
  8. }それ以外 {  
  9. ログ出力()  
  10. }  
  11. }  
  12. }

上記のように、サービス B への API リクエストが失敗すると、最大 3 回の再試行が開始されます。 3 回失敗すると、ログを出力して実行を継続するか、上位層にエラーをスローします。

このアプローチでは、次のような問題が発生します。

  1. サービス B への呼び出しは成功しますが、ネットワーク タイムアウトのため、現在のサービスはそれを失敗と見なし、再試行を続行するため、サービス B は 2 つの同一データを生成します。
  2. サービス B が利用できなかったため、サービス B への呼び出しは失敗しました。 3回再試行しても、まだ失敗しました。前のコードで現在のサービスによって DB に挿入されたレコードはダーティ データになりました。
  3. 再試行すると、この通話のアップストリーム遅延が増加します。ダウンストリームの負荷が大きい場合、再試行するとダウンストリーム サービスへの負荷が増加します。

最初の問題は、サービス B の API がべき等性をサポートするようにすることで解決できます。

2 番目の問題: バックグラウンドのタイミング手順を通じてデータを修正できますが、これは適切な解決策ではありません。

3 番目の質問: これは、再試行をブロックすることで一貫性と可用性を向上させるために避けられない犠牲です。

再試行のブロックは、ビジネスが一貫性の要件に敏感でないシナリオに適しています。データの一貫性が求められる場合は、問題を解決するために追加のメカニズムを導入する必要があります。

非同期キュー

ソリューションの進化のプロセスでは、キューを導入することが一般的で、より優れた方法です。次の例:

  1. m : = db .Insert(sql) です。  
  2. err : = mq .Publish("B-Service-topic",m)

現在のサービスがデータを DB に書き込んだ後、メッセージを MQ にプッシュし、独立したサービスが MQ を消費してビジネス ロジックを処理します。ブロッキング再試行と比較すると、MQ は通常のビジネス サービスよりもはるかに安定していますが、ネットワークの問題、現在のサービス停止などにより、MQ にメッセージをプッシュする呼び出しが失敗する可能性が依然としてあります。この場合も、ブロッキング再試行と同じ問題が発生します。つまり、DB 書き込みは成功しますが、プッシュは失敗します。

理論的に言えば、分散システムでは、複数のサービス呼び出しを伴うコードでは必ずこのような状況が発生し、長期的には呼び出しの失敗が確実に発生します。これも分散システム設計における難しさの 1 つです。さらに、MQ シリーズのインタビューの質問と回答がすべて整理されています。 WeChat で Java テクノロジー スタックを検索し、バックグラウンドで「インタビュー」を送信してオンラインで読むことができます。

TCC 報酬業務

トランザクションの要件があり、分離が不便な場合は、TCC 補償トランザクションを選択する方がよいでしょう。

TCC は各サービス コールを 2 つの段階と 3 つの操作に分割します。

  • フェーズ 1: 操作の試行: 在庫の確認や保留など、ビジネス リソースをテストし、リソースを予約します。
  • フェーズ 2: 操作の確認: Try 操作のリソース予約を送信して確認します。たとえば、在庫源泉徴収を控除に更新します。
  • フェーズ 2、キャンセル操作: Try 操作が失敗すると、保留されていたリソースが解放されます。たとえば、在庫の保留を戻します。

TCC では、各サービスに上記の 3 つの操作用の API を実装する必要があります。サービスが TCC トランザクションにアクセスする前は 1 回の呼び出しで完了していた操作は、2 段階、3 つの操作で完了する必要があります。

たとえば、ショッピング モール アプリケーションでは、次の疑似コードに示すように、A 在庫サービス、B 金額サービス、C ポイント サービスを呼び出す必要があります。

  1. m : = db .Insert(sql) です。  
  2. aResult、aErr : = A.Try (m)  
  3. bResult、bErr : = B.Try (m)  
  4. cResult、cErr : = C.Try (m)  
  5. cErr != nil の場合 {  
  6. A.キャンセル()  
  7. B.キャンセル()  
  8. C.キャンセル()  
  9. } それ以外 {  
  10. A.確認()  
  11. B.確認()  
  12. C.確認()  
  13. }

コードは、A、B、C サービス API を呼び出して、リソースをチェックおよび予約します。すべてが成功を返すと、確認操作が送信されます。 C サービスの Try 操作が失敗した場合、A、B、C の Cancel API が呼び出され、予約済みのリソースが解放されます。

TCC は、分散システム内の複数のサービスと複数のデータベースにわたるデータの一貫性の問題を解決します。ただし、TCC 方式には、上記のセクションで説明した通話障害など、実際の使用時に注意する必要がある問題がまだいくつかあります。

空のリリース

上記のコードでは、C.Try() が実際に失敗した場合、以下の冗長な C.Cancel() 呼び出しによってリソースは解放されますが、ロックはされません。これは、現在のサービスでは、呼び出しの失敗によって実際に C リソースがロックされたかどうかを判断できないためです。呼び出されない場合、実際には成功しますが、ネットワーク上の理由により失敗が返され、C のリソースがロックされ、解放されなくなります。

実稼働環境では空のリリースが頻繁に発生します。 TCC トランザクション API を実装する場合、サービスは空のリリースの実行をサポートする必要があります。

タイミング

上記のコードでは、C.Try() が失敗すると、C.Cancel() が呼び出されます。ネットワーク上の理由により、C.Cancel() 要求が最初に C サービスに到着し、C.Try() 要求が後に到着する可能性があります。これにより、空の解放問題が発生し、C のリソースがロックされて解放されなくなります。

したがって、C サービスはリソースを解放した後、Try() 操作を拒否する必要があります。具体的な実装では、一意のトランザクション ID を使用して、最初の Try() とリリース後の Try() を区別できます。

通話に失敗しました

一般的なネットワーク上の理由などにより、呼び出しプロセス中にキャンセルと確認が失敗する場合があります。

Cancel() または Confirm() 操作が失敗すると、リソースがロックされ、解放されなくなります。この状況に対する一般的な解決策は次のとおりです。

  1. 再試行をブロックします。しかし、ダウンタイムや頻繁な障害など、同じ問題が存在します。
  2. ログとキューに書き込み、別の非同期サービスに自動または手動で介入させて処理させます。しかし、それでも問題は発生し、ログやキューの書き込み時に障害が発生します。

理論的には、2 つの非アトミック コード セグメントとトランザクション コード セグメントに中間状態が存在し、中間状態の存在は障害が発生する可能性があることを意味します。

ローカルメッセージテーブル

ローカル メッセージ テーブルはもともと eBay によって提案されました。ローカル メッセージ テーブルとビジネス データ テーブルを同じデータベースに配置できるため、トランザクション特性を満たすためにローカル トランザクションを使用できます。

具体的なアプローチは、ローカル トランザクションにビジネス データを挿入するときにメッセージ データを挿入することです。その後、後続の操作を実行します。他の操作が成功した場合は、メッセージを削除します。失敗した場合は、メッセージを削除せず、非同期でメッセージをリッスンして再試行を続けます。

ローカル メッセージ テーブルは優れたアイデアであり、さまざまな方法で使用できます。

MQと協力する

擬似コードの例:

  1. messageTx : = tc .NewTransaction("注文")  
  2. messageTxSql : = tx.TryPlan ("コンテンツ")  
  3. m,err : = db .InsertTx(sql,messageTxSql) です 
  4. err!=nilの場合{  
  5. エラーを返す 
  6. }  
  7. aErr : = mq .Publish("B-サービス-トピック",m)  
  8. if aErr!=nil { // MQへのプッシュに失敗しました 
  9. messageTx.Confirm() // 確認のためにメッセージステータスを更新します 
  10. }それ以外 {  
  11. messageTx.Cancel() // メッセージを削除する 
  12. }   
  13. //確認メッセージを非同期に処理し、プッシュを続行します 
  14. onMessage関数(task *Task){  
  15. err : = mq .Publish("B-Service-topic", task.Value())  
  16. エラー== nilの場合{  
  17. メッセージ送信キャンセル()  
  18. }  
  19. }

上記のコードでは、messageTxSql はローカル メッセージ テーブルに挿入される SQL ステートメントです。

  1. `tcc_async_task` に挿入 (`uid`,`name`,`value`,`status`)  
  2. 値('?'、'?'、'?'、'?')

これはビジネス SQL と同じトランザクションで実行され、成功するか失敗します。

成功した場合はキューにプッシュされます。プッシュが成功した場合は、messageTx.Cancel() を呼び出してローカル メッセージを削除します。プッシュが失敗した場合は、メッセージを確認済みとしてマークします。ローカル メッセージ テーブルのステータスには、試行と確認の 2 つの状態があります。どのような状態であっても、OnMessage はそれを監視し、再試行を開始できます。

ローカル トランザクションにより、メッセージとサービスがデータベースに書き込まれることが保証されます。その後は、ダウンタイムやネットワーク プッシュの失敗に関係なく、非同期監視によって後続の処理が実行され、メッセージが MQ にプッシュされることが保証されます。

MQ は、消費者サービスに到達することを保証します。 MQ の QOS 戦略を使用すると、コンシューマー サービスはトランザクションを処理するか、次のビジネス キューに引き続き配信できるため、トランザクションの整合性が確保されます。

サービスコールに協力する

擬似コードの例:

  1. messageTx : = tc .NewTransaction("注文")  
  2. messageTxSql : = tx.TryPlan ("コンテンツ")  
  3. 本文、エラー: = db .InsertTx(sql,messageTxSql)  
  4. err!=nilの場合{  
  5. エラーを返す 
  6. }  
  7. aErr : =リクエスト.POST("B-Service",本文)  
  8. if aErr!=nil { // Bサービスの呼び出しに失敗しました 
  9. messageTx.Confirm() // 確認のためにメッセージステータスを更新します 
  10. }それ以外 {  
  11. messageTx.Cancel() // メッセージを削除する 
  12. }  
  13. // 確認メッセージまたは試行メッセージを非同期的に処理し、B-Service の呼び出しを継続します。  
  14. onMessage関数(task *Task){  
  15. // request.POST("B-Service",本文)  
  16. }

これは、MQ を導入せずに、ローカル メッセージ テーブル + 他のサービスを呼び出す例です。この方法は、非同期再試行とローカル メッセージ テーブルを使用してメッセージの信頼性を確保し、再試行のブロックによって発生する問題を解決し、日常の開発でより一般的になっています。

DB に書き込むローカル操作がない場合は、ローカル メッセージ テーブルにのみ書き込み、OnMessage で処理することもできます。

  1. messageTx : = tc .NewTransaction("注文")  
  2. messageTx : = tx .Try("content")  
  3. aErr : =リクエスト.POST("B-Service",本文)
  4. // ....

メッセージの有効期限

ローカル メッセージ テーブルの Try および Confirm メッセージ ハンドラーを構成します。

  1. TCC.SetTryHandler(OnTryMessage())  
  2. TCC.SetConfirmHandler(OnConfirmMessage())

メッセージ処理機能では、現在のメッセージ タスクに時間がかかりすぎているかどうかを判断する必要があります。たとえば、1 時間再試行してもまだ失敗する場合は、電子メール、テキスト メッセージ、ログ アラームなどを送信して手動介入を可能にすることを検討してください。

  1. OnConfirmMessage関数(task *tcc.Task) {  
  2. time.Now().Sub(task.CreatedAt) > time.Hour の場合 {  
  3. err : = task .Cancel() // メッセージを削除し、再試行を停止します。  
  4. // doSomeThing() アラーム、手動介入 
  5. 戻る 
  6. }  
  7. }

Try 処理機能では、Try 状態のメッセージは作成されたばかりで、まだ確認、送信、または削除されていない可能性があるため、現在のメッセージ タスクが短すぎるかどうかを判断する必要もあります。これにより、通常のビジネス ロジックの実行が繰り返され、成功した呼び出しも再試行されます。この状況をできるだけ回避するには、メッセージの作成時間が非常に短いかどうかを検出します。もしそうなら、スキップできます。

再試行メカニズムは、ビジネス ロジックの観点から、必然的に下流 API のべき等性に依存します。処理しないことも可能ですが、通常のリクエストに干渉しないように設計する必要があります。さらに、Java コア テクノロジーのチュートリアルとサンプル ソース コードも推奨されます: https://github.com/javastacks/javastack

独立したメッセージングサービス

独立メッセージ サービスは、ローカル メッセージ テーブルを独立したサービスに抽出する、ローカル メッセージ テーブルのアップグレード バージョンです。すべての操作の前に、メッセージ サービスにメッセージを追加します。後続の操作が成功した場合は、メッセージを削除します。失敗した場合は、確認メッセージを送信します。

次に、非同期ロジックを使用してメッセージをリッスンし、対応する処理を実行します。これは基本的に、ローカル メッセージ テーブルの処理ロジックと同じです。ただし、メッセージ サービスへのメッセージの追加はローカル操作と同じトランザクションに含めることができないため、メッセージは正常に追加されたものの後で失敗する状況が発生する可能性があります。この場合、メッセージは役に立ちません。

以下にシナリオ例を示します。

  1. エラー: =リクエスト.POST("Message-Service",本文)  
  2. err!=nilの場合{  
  3. エラーを返す 
  4. }  
  5. aErr : =リクエスト.POST("B-Service",本文)  
  6. aErr!=nilの場合{
  7. aErrを返す 
  8. }

この無意味なメッセージでは、メッセージ サービスがメッセージが正常に実行されたかどうかを確認する必要があります。そうでない場合は削除されます。はいの場合は、後続のロジックが引き続き実行されます。ローカル トランザクション テーブルの try および confirm と比較すると、メッセージ サービスには、先頭に追加の状態 prepare があります。

MQ トランザクション

RocketMQ などの一部の MQ 実装ではトランザクションがサポートされています。 MQ トランザクションは、まったく同じロジックを持つ、独立したメッセージ サービスの特定の実装として考えることができます。

すべての操作の前に、メッセージが MQ に配信されます。後続の操作が成功した場合はメッセージの送信を確定し、失敗した場合はメッセージを削除してキャンセルします。 MQ トランザクションは準備状態でも存在し、ビジネスが成功したかどうかを確認するには MQ コンシューマー処理ロジックが必要です。

要約する

分散システムの実践の観点から、データの一貫性を確保するためには、追加のメカニズム処理を導入する必要があります。

TCC の利点は、ビジネス サービス層で動作し、特定のデータベースに依存せず、特定のフレームワークと結合されておらず、リソース ロックの粒度が柔軟であるため、マイクロサービス シナリオに非常に適していることです。欠点は、各サービスが 3 つの API を実装する必要があり、これによりビジネスへの重大な侵入と変更が発生し、さまざまな障害例外の処理が必要になることです。開発者がさまざまな状況に完全に対処するのは困難です。 Alibaba の Fescar のような成熟したフレームワークを見つけることで、コストを大幅に削減できます。

ローカル メッセージ テーブルの利点は、シンプルで、他のサービスの変換に依存せず、サービス呼び出しや MQ と組み合わせて使用​​でき、ほとんどのビジネス シナリオでより実用的であることです。欠点は、ローカル データベースにビジネス テーブルと結合された追加のメッセージ テーブルがあることです。この記事のローカル メッセージ テーブル メソッドの例は、著者が作成したライブラリから取得したものです。興味のある学生はhttps://github.com/mushroomsir/tccを参照してください。

MQ トランザクションと独立したメッセージ サービスの利点は、トランザクションの問題を解決するために共通のサービスが抽出され、各サービスがメッセージ テーブルを持ち、サービスと結合されてサービス自体の処理の複雑さが増す状況を回避できることです。欠点は、トランザクションをサポートする MQ が非常に少ないことです。また、各操作の前に API を呼び出してメッセージを追加すると、呼び出し全体の待ち時間が増加します。これは、通常の応答を伴うほとんどのビジネス シナリオでは不要なオーバーヘッドになります。

<<:  クラウドコンピューティングは新エネルギー車よりも排出量を削減する

>>:  アリババは、NvidiaやGoogleと比較してコンピューティング電力消費を80%削減する1兆パラメータのAIモデルM6をリリースしました。

推薦する

Dell Technologies のハイブリッド クラウド自動化とデジタル化が主流に

過ぎ去ったばかりの2021年は、テクノロジーがこれまで以上に重要であることを間違いなく証明しました。...

クラウドネイティブではコードとしての監視とコードとしてのインフラストラクチャが必要な理由

[51CTO.com クイック翻訳]急速に台頭している 2 つのテクノロジー、コードとしてのモニタリ...

百度指数が実際のトラフィックに比例しない5つの理由

Baidu の最適化を行っている友人にとって、Baidu Index は常に無視できないデータ情報で...

サービス メッシュは本当にクラウド ネイティブ アプリケーションに最適ですか?

マイクロサービス アーキテクチャを実装する企業が増えるにつれて、コミュニティにおけるサービス メッシ...

GIF で Distributed Raft を説明する

[[378468]] 1. いかだの概要Raft アルゴリズムは、分散システム開発に適したコンセンサ...

ゴン・ハイヤンの手放し:自分を救うために腕を切り落とし、プロのマネージャーに引き継がせる

ゴン・ハイヤン新浪テクノロジー 神雲芳創業者のGong Haiyan氏の辞任と分散化により、Jiay...

Godaddy エコノミースペース、100GB スペース + 1 ドメイン名で年間 12 ドル

Godaddy では、Godaddy のエコノミー モデル仮想ホストを 1 年間 + 無料ドメイン名...

Dapr 入門チュートリアル: 公開と購読

先ほど、Dapr でサービス呼び出しを行う方法と、最も単純な状態管理について学習しました。このセクシ...

ニュース寄稿プロモーションのメリットは何ですか?

最近では、フォーラムプロモーション、ブログプロモーション、質疑応答プロモーション、電子メールプロモー...

民間医療業界におけるWeChatマーケティングのメリットとデメリット

WeChat マーケティングに関しては、誰もがよく知っていると思います。ただし、WeChat 上のユ...

Weiboマーケティングを利用するTaobao販売者への提案

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービスSEO担当者として、ウェ...

注: Bluehostの公式中国語サイトの説明

最近、一部のネットユーザーから、bluehostが中国語のウェブサイトを立ち上げたという報告がありま...

慌てないで: Kubernetes と Docker

バージョン 1.20 以降、Kubernetes はコンテナ ランタイムとして Docker を使用...

desivps: 米国ロサンゼルスのデータセンターの VPS を簡単にレビューして、desivps がどのように機能するかを確認します。

desivps は最近、中国のお客様向けに特別プロモーション VPS を開始しました (年間 6 回...

ショックホスティングの月額料金は 3.49 ドル/KVM/512m メモリ/15g SSD/750g トラフィック

shockhosting.net の主な事業は仮想ホスティングと VPS です。すべての VPS サ...