分散一貫性を1つの記事で徹底的に理解する

分散一貫性を1つの記事で徹底的に理解する

分散システムにおけるデータの一貫性は、次の 2 つのカテゴリに分けられます。

  1. トランザクションの一貫性: 複数のノードが操作を実行する場合、すべてのノードが到達する最終状態は一貫しています。これには、操作の正確性を確保し、データの不整合を回避するための調整が必要です。
  2. レプリカの一貫性: データの複数のコピーは一貫性を保つ必要があり、そのためには、データが変更されたときに、データが同期されなくなるのを避けるために、すべてのコピーがタイムリーに更新されるようにする必要があります。

定義は比較的抽象的です。例を挙げて説明しましょう:

  1. トランザクションの一貫性: 電子商取引プラットフォームはクーポンを使用して注文を行います。

写真

  1. 注文を正常に行うには、クーポンが「ロック」状態になっている必要があります。
  2. 支払いを正常に行うには、クーポンが「使用済み」の状態である必要があります。
  3. 注文がキャンセルされた場合、クーポンを「保留中」ステータスに戻す必要があります。
  4. クーポンと注文は「取引的に一貫している」ため、両者の間には強い相関関係があります。
  1. レプリカの一貫性:
  • MySQL マスター スレーブ レプリケーション: マスター データベースでデータ操作を実行した後、マスター データベースでのデータ操作を 1 つ以上のスレーブ データベースに同期することを指します。スレーブ データベースは、スレーブ データベースのデータがマスター データベースのデータと一致するように、マスター データベースと同期する必要があります。
  • Redis と MySQL の一貫性: Redis をストレージとして使用する場合、MySQL はマスター ノード、Redis はスレーブ ノードと見なすことができます。 MySQL データが変更されると、自動的に Redis に同期され、データの一貫性が維持されます。
  1. 画像

[注] この記事では、「トランザクションの一貫性」と「マルチコピーの一貫性」に焦点を当てています。詳細については、キャッシュまたは ES の記事を参照してください。

1. データベーストランザクションの支配から抜け出す

リレーショナル データベースでは、トランザクションとは、すべてが成功するかすべてが失敗する一連のデータベース操作を指します。トランザクションにより、特定のデータ操作の一貫性を確保できます。操作が失敗すると、ロールバックされます。つまり、実行された操作は元に戻され、データは操作前の状態に復元されます。

トランザクションの一貫性に関しては、データベース トランザクションの ACID について話す必要があります。ACID は、データベース トランザクションの 4 つの主要な特性、つまり原子性、一貫性、独立性、および永続性を指します。

  1. アトミック性: トランザクションはアトミック操作として考慮する必要があります。つまり、トランザクション内のすべての操作は正常に実行されるか、すべて失敗してロールバックされます。トランザクション実行中にエラーが発生した場合、すべての変更操作はロールバックされ、データは破損しません。
  2. 一貫性: トランザクションが実行される前と実行された後で、データの一貫性が維持される必要があります。すべてのデータ変更操作では、データベースの制約、トリガー、その他のルールに違反せず、データの整合性を維持する必要があります。
  3. 分離: 複数のトランザクションが同時に同じデータに対して操作する場合、トランザクションは互いに分離され、互いに干渉しないようにする必要があります。データベース システムは、同時実行時のトランザクション実行の結果がシリアル実行の結果と一致することを保証する必要があります。
  4. 耐久性: トランザクションが完了した後、データベースに加えられたすべての変更は永続的に保存される必要があります。システムがクラッシュしたり再起動したりした場合でも、変更されたデータは利用可能であるはずです。

銀行振込アプリケーションは、ACID モデルの典型的なアプリケーション シナリオです。ユーザー A がユーザー B に 1,000 元を送金したいとします。送金プロセスはトランザクションであり、原子性、一貫性、独立性、永続性という 4 つの主要な特性があります。

  1. アトミック性: 転送プロセスには、アカウント A から 1,000 元を減算し、アカウント B に 1,000 元を追加するという合計 2 つの操作が含まれます。これら 2 つの操作のいずれかが失敗すると、トランザクション全体が失敗し、ロールバックされます。
  2. 一貫性: 振替前と振替後のすべての口座の合計残高は同じままであり、残高不足や残高超過は発生しません。
  3. 分離: 2 つの転送トランザクションが同時に開始された場合、各トランザクションが独自のデータにのみアクセスし、相互に干渉しないようにする必要があります。
  4. 永続性: 転送が完了したら、システムがクラッシュしたり再起動したりしてもデータが引き続き使用できるように、データを変更するトランザクションをディスクに書き込む必要があります。

データベース トランザクションはプログラマーにとって間違いなく強力なツールですが、さまざまな理由から、このツールは私たちからどんどん遠ざかっています。

  1. 負荷の課題: ビジネスの急速な成長に伴い、データベース内のデータ量または負荷が単一インスタンスの上限に達します。現時点では、次のとおりです。

垂直分割: ユーザー インスタンスと注文インスタンスを分割するなど、異なるテーブルを異なるデータベース インスタンスに配置します。

写真

水平分割: データ量が単一テーブルの最大容量を超えると、データは Order-1 インスタンスと Order-2 インスタンスなどの異なるデータベースに分割されます。

写真

垂直 + 水平分割: 最初に垂直分割を実行し、次に水平分割を実行します。

写真

  1. マイクロサービスの課題: マイクロサービスは、特に Spring Boot と Spring Cloud の人気により、システムの事実上のアーキテクチャになりました。

マイクロサービスの「自律性」には、他のサービスとデータストレージを共有することを避けるために、各マイクロサービスが独自の独立したデータストレージを持つことが必要であり、それによってサービス間の結合が低減されます。

マイクロサービスは、サービス検出、負荷分散などを通じてサービス間の関係を切り離し、各サービスが独立した自律性を持つようにします。

写真

どのような条件がトリガーされたかに関係なく、データベース間のトランザクションが生成されるため、システム設計の難易度が高まります。

2. 共通の一貫性保証メカニズム

これまでの研究者たちは、この問題に対して、特にリレーショナル データベースなど、さまざまな解決策を提案してきました。

MySQL トランザクションの一貫性

MySQL の実装に精通している人は、MySQL が Redo ログと Undo ログを通じてトランザクションの一貫性を実装していることを知っています。

  1. Redo ログ: Redo ログには、挿入、更新、削除などの操作を含む、トランザクションによってデータベースに加えられた変更が記録されます。トランザクションがコミットされる前にディスクに書き込まれます。障害によりシステムがクラッシュした場合、MySQL は Redo ログからデータを回復します。
  2. 元に戻すログ: 元に戻すログは、トランザクションによってデータベースに加えられた変更の「以前の操作」を記録し、トランザクションがロールバックされたときにトランザクションによって行われた変更を元に戻すために使用されます。トランザクションが更新を実行すると、MySQL はまず変更前のデータを Undo ログに保存します。トランザクションをロールバックする必要がある場合、MySQL は Undo ログのレコードに基づいてデータを変更前の状態に復元します。

詳細は、次の図に示されています。

写真

図から次のことがわかります。

  1. 各 DML ステートメントは、対応する Redo ログと Undo ログを生成します。

REDO ログは肯定的な変更を記録します。

UNDO ログはリバース リカバリを記録します。

  1. トランザクションのコミットは、すべての Redolog を適用して、変更を前方に保持します。
  2. トランザクション ロールバックは、リバース リカバリのすべての Undolog を適用します。

その中には、2 つのコア プロセスがあることがわかります。

  1. フォワード補正: REDO ログには、トランザクションがコミットされる前のトランザクション実行プロセスとデータ変更が記録されます。データは、REDO ログを通じて復元され、前方補償を実現できます。
  2. 後方補正: UNDO ログには、トランザクション実行中のデータの変更が記録され、トランザクションをロールバックして後方補正を実行するために使用できます。

2 つの補償メカニズムに加えて、補償メカニズムを調整するために使用される「補償マネージャー」という重要なコンポーネントもあります。

2.2. 2PCとXA

2PC (2 フェーズ コミット) と XA は、分散トランザクションでよく使用されるプロトコルとインターフェイスです。

  • 2PC は、分散システム内の複数の参加者間でトランザクションのコミットまたはロールバックを調整するために使用される分散トランザクション プロトコルです。準備フェーズと提出フェーズの 2 つのフェーズで構成されます。準備段階では、参加者は正常に提出できるかどうかをコーディネーターに通知します。正常に送信できる場合、すべての参加者は送信フェーズでトランザクションを送信します。 1 人の参加者が正常に送信できなかった場合、すべての参加者がロールバックする必要があります。
  • XA は、アプリケーションが分散トランザクションに参加し、トランザクション マネージャーと連携してトランザクションの一貫性を確保できるようにするアプリケーション プログラミング インターフェイス (API) のセットです。 XA インターフェイスには、分散トランザクションの調整と管理に使用される XA トランザクション、XA リソース、および XA リソース マネージャーの 3 つのインターフェイスが含まれます。

MySQL は、2 フェーズ コミット (2PC) プロトコルを使用して、Redolog と Binlog 間のデータの一貫性を確保し、関連するすべてのノード (Redolog と Binlog を含む) でトランザクションが実行されたときに、すべてのトランザクションが正常にコミットされるか、ロールバックされることを保証します。

2PC は、トランザクション参加者が 2 人のシナリオにのみ適用できますが、XA は、図に示すように、トランザクション参加者が複数いるシナリオに適用できます。

写真

XA はインターフェースのセットを定義します:

  • XA リソース マネージャー (RM): データベース、メッセージ キューなどの分散トランザクション リソースを管理するために使用されます。
  • XA トランザクション マネージャー (TM): さまざまなリソース マネージャーのトランザクション処理を調整するために使用されます。
  • XA インターフェイス: XA インターフェイスを使用すると、アプリケーションは、トランザクションの開始、コミット、ロールバックなどの操作を含む分散トランザクションの調整に参加できます。

対応するトランザクションのコミットおよびロールバックのプロセスは次のとおりです。

  • アプリケーションは XA インターフェイスを介して分散トランザクションを開始し、XA トランザクション マネージャーはトランザクションに一意のグローバル トランザクション ID を割り当てます。
  • アプリケーションは、XA インターフェイスを使用して、複数の XA リソース マネージャーが関与する可能性のある分散トランザクションの一部として特定の操作を登録します。
  • アプリケーションがトランザクションをコミットするコードを実行すると、XA トランザクション マネージャーはまずさまざまな XA リソース マネージャーと調整して、これらのリソース マネージャーがトランザクションをコミットできるかどうかを確認します。
  • すべてのリソース マネージャーがトランザクションをコミットできる場合、XA トランザクション マネージャーは各リソース マネージャーにトランザクションをコミットする要求を送信し、応答を待機します。
  • いずれかのリソース マネージャーがトランザクションをコミットできない場合、XA トランザクション マネージャーは各リソース マネージャーにトランザクションをロールバックする要求を送信し、応答を待機します。
  • すべてのリソース マネージャーがトランザクションのコミットまたはロールバックの要求に応答すると、XA トランザクション マネージャーはアプリケーションにトランザクションの状態 (コミットまたはロールバック) を通知し、リソースを解放します。

2PC (アップグレードされた 3PC を含む) では、トランザクション実行プロセス全体にわたってリソースをロックする必要があります。分散環境では、システム応答時間が大幅に増加し、システム全体のスループットが低下します。実際の業務ではあまり使用されません。

2.3. TCC

TCC は分散トランザクション ソリューションを実装するための効果的な方法であり、実際の業務に真に適用できる主要なソリューションでもあります。

写真

TCC (try-confirm-cancel) は、分散トランザクションを 3 つのプロセスに分割する分散トランザクション ソリューションです。

  • 試行操作: 分散トランザクションで操作を実行して、すべての参加者がトランザクションを実行する準備ができているかどうかを確認します。準備が整ったら、リソースをロックし、操作の確認またはキャンセルを待ちます。
  • 操作の確認: 分散トランザクションでの操作の実行を確認し、すべての参加者の操作をコミットします。エラーが発生した場合、すべての操作がロールバックされ、ロックされたリソースが解放されます。
  • 操作のキャンセル: 分散トランザクション内の操作をキャンセルし、すべての参加者の操作をロールバックし、ロックされたリソースを解放します。

TCC の操作プロセスは次のとおりです。

  • アプリケーションはコーディネータに分散トランザクションを要求し、実行する必要があるすべての操作を送信します。
  • コーディネータは、TCC の分散トランザクション処理戦略に従って一意の分散トランザクション ID を作成し、各参加者に割り当てます。
  • 各参加者は Try 操作を実行し、アクセスする必要があるリソースをロックします。
  • コーディネーターは、すべての参加者が操作を実行する準備ができているかどうかを確認します。参加者全員が準備ができたら、確認フェーズに入ります。
  • 確認フェーズでは、各参加者が操作の実行を確認し、結果をコーディネータに送信します。
  • エラーが発生した場合、コーディネーターはすべての操作をロールバックし、ロックされたリソースを解放します。それ以外の場合、トランザクションはすべての当事者間で実行されたことが確認され、リソースが解放され、トランザクションが終了します。
  • いずれかの参加者が試行フェーズで失敗した場合、キャンセル フェーズに入ります。
  • キャンセル フェーズでは、すべての関係者がすべての操作をキャンセルし、ロックされたリソースを解放します。
  • コーディネーターは、例外を処理するために各段階での操作を記録します。

TCC は、手動介入によって例外を処理する補償トランザクション メカニズムです。柔軟性に優れ、さまざまなタイプのアプリケーション シナリオに適しています。

2.4.トランザクションの一貫性の性質

私は一貫性の解決策をたくさん読んできました。何かパターンは見つかりましたか?

コアコンポーネントは基本的に同じです。

  • アプリケーション: トランザクション マネージャーとリソース管理機能を使用してトランザクションの一貫性を確保する開発されたアプリケーション システムとして簡単に理解されます。
  • トランザクション マネージャー: トランザクション コーディネーターは、アプリケーションからの要求を受け取り、複数のリソース マネージャーを調整し、フォワード補償とリバース補償を共同で完了します。
  • リソース マネージャー: アプリケーションおよびトランザクション マネージャーが使用するフォワード補償インターフェイスとリバース補足インターフェイスを提供する単一のリソース マネージャー。

コアプロセスは基本的に同じです。

  • 肯定的な補償: 申請プロセスが前進し、最終的にある状態から別の状態に移行します。
  • 逆補正: アプリケーション プロセスが後方に移動し、すべての操作がロールバックされて以前の状態に戻ります。

簡単に言うと、トランザクションの一貫性とは、参加ノードを調整して、関連するすべての操作が正常に実行されるか、まったく実行されないかのいずれかを確実にすることで、分散トランザクションの送信またはロールバックを実装することです。異なる実装方法は単に異なるツールであり、実装の考え方は基本的に同じです。

3. ビジネス一貫性保証メカニズム

先人たちは私たちに十分なツールを提供してくれました。これらのツールをより有効に活用するには、ビジネス シナリオを詳細に分析する必要があります。

ビジネス システムの一貫性とは、複数のシステムまたは異なる環境内の異なるユーザーまたはシステム操作によって生成されるデータが論理的に同じであることを意味します。その本質は、さまざまなシステムやユーザーによって生成されたデータがどのような状況でも一貫していること、そしてシステム内のすべての操作が期待どおりに実行されることを保証することです。ビジネス システムの一貫性は、データの正確性と信頼性を確保するための重要な要素です。データのエラーや損失を効果的に回避し、ビジネス システムの可用性と信頼性を向上させ、企業の持続可能な発展を確保できます。 \\下の図に示すように:

写真

再試行可能なトランザクション間に依存関係がない場合、次のように並列で実行できます。

写真

複雑なビジネス プロセスでは、トランザクションは次の 3 つのカテゴリに分類できます。

  • 重要なトランザクション: システム内で最も重要な操作を指します。トランザクションの送信に失敗した場合は、ロールバックされます。トランザクションが正常に送信された場合、それは事実となり、ロールバックすることはできません。
  • 補償可能なトランザクション: 重要なトランザクションの前のトランザクション操作を指し、通常は順方向と逆方向の 2 セットの操作を提供します。転送操作が失敗した場合、または重要なトランザクションが失敗した場合は、逆の順序でリバース インターフェイスが呼び出され、操作がロールバックされます。
  • 再試行可能なトランザクション: キー トランザクション後のトランザクション操作を指します。キー トランザクションが正常に送信された場合、その事実が判断され、ダウンストリームは再試行してトランザクションを完了します。

分散システムにおける注文プロセスを例に挙げてみましょう。

写真

  • 主要なトランザクション: 注文の確定とユーザー情報のデータベースへの保存。保存に失敗した場合、操作された在庫とクーポンは逆復元されます。保存が成功した場合、再試行によって下流のトランザクションの一貫性が保証されます。
  • 補償対象取引: クーポンおよび在庫サービスによって提供される順方向および逆方向の操作を指します。前方操作は後方操作を通じて回復できます。
  • 再試行可能なトランザクション: 自動タスクキャンセルの追加、操作ログの保存、MQ の送信を指します。注文データが正常に保存されると、これら 3 つは継続的な再試行によって実行されることが保証されます。

3.1.重要な事項

重要なトランザクション: 分散システムでは、トランザクションが正常に送信された場合にのみ、システム全体でトランザクションが成功したとみなされます。このトランザクションが失敗すると、システム全体が以前の状態にロールバックされます。たとえば、支払い、注文の提出など。

重要なトランザクション使用シナリオの観点から、最も適切なツールはリレーショナル データベースのトランザクション保証です。

写真

  • トランザクションの送信が成功しました: プロセス全体が補正され、再試行可能なトランザクションがプッシュされ、継続的な再試行を通じてビジネス ロジックが完了します。
  • トランザクション送信の失敗: プロセス全体のロールバックをトリガーし、トランザクションのロールバック インターフェイスを逆の順序で呼び出してステータスを復元します。

3.2.補償対象取引

補償対象取引とは、一部のサブオペレーションの実行に失敗した場合、特定のビジネス目標を達成するために後続の補償オペレーションによって修復できる特定のビジネスオペレーションを指します。例えば、資金取引において、口座残高が不足して支払いサブ操作が失敗した場合、注文をキャンセルするなどの補償操作によって取引の正確性を保証することができます。

補償可能なトランザクションの場合、次の 2 セットの操作を提供する必要があります。

  1. プラス面: 在庫ロックなどの標準的な業務操作
  2. リバース: ロックされた在庫の解放など、フォワード操作の回復操作
3.2.1.シータ

Seata は、分散システムにおけるトランザクションの一貫性の問題を解決するために設計されたオープン ソースの分散トランザクション ソリューションです。従来の分散システムでは、サービス間のデータのやり取りや操作が独立して実行されるため、データの不整合が発生しやすくなります。これにより、データの損失、重複送信など、システム内でさまざまな異常な状況が発生し、システムの安定性と信頼性に影響します。

Seata は、分散トランザクションの一貫性の問題を解決するためのさまざまなソリューションを提供します。これらには、XA モード、TCC モード、SAGA モードが含まれます。

  • XA モードは、データベース ベースのトランザクション管理モードです。 Seata は、データベースと対話することで分散トランザクションの一貫性を実現します。このモードは、金融や電子商取引など、データの一貫性に対する要件が高いビジネス シナリオに適しています。ただし、データベースとやり取りする必要があるため、このモードのパフォーマンスは比較的低くなります。
  • AT モード (アプリケーション層に基づく 2 フェーズ コミット方式): AT モードは、アプリケーション プログラムにトランザクション セマンティクスを埋め込み、必要なロックを調整および維持することで、複数のビジネス ノード間の複数のデータベース テーブルにわたるトランザクションを実装します。電子商取引の注文など、リレーショナル データベースのアプリケーション シナリオに適用できます。
  • TCC モードは、補償に基づくトランザクション管理モードです。 Seata は、リソースの予約、実行の試行、実行の確認、実行のロールバックという 4 つの段階を通じて分散トランザクションの一貫性を実現します。このモードは、ゲームやソーシャル ネットワーキングなど、高いパフォーマンス要件が求められるビジネス シナリオに適しています。ただし、このパターンは複数の非同期通信を必要とするため、より複雑です。
  • SAGA モードは、イベント駆動型のトランザクション管理モードです。 Seata は、大規模な分散トランザクションを複数の小さなローカル トランザクションに分割し、非同期メッセージングを通じて分散トランザクションの一貫性を実現します。このモードは、マイクロサービス アーキテクチャのシステムなど、パフォーマンスと可用性に対する要件が高いビジネス シナリオに適しています。ただし、このパターンは複数の非同期通信と状態管理が必要となるため、より複雑になります。

Seata は、トランザクション ログ、障害回復、動的拡張などの重要な機能も提供しており、ユーザーがフレームワークを使用して分散トランザクションの一貫性の問題を簡単に解決できるようになります。同時に、Seata は高性能、高可用性、使いやすさという特徴も備えており、さまざまなシナリオのニーズを満たすことができます。

[注] ご興味がございましたら、Seata の公式ドキュメントをご覧ください。

3.2.2.コンテキスト + ロールバック

Seata は優れていますが、ミドルウェアを導入するとシステムの複雑さが大幅に増加します。それほど厳密でないシナリオや、運用および保守能力が不十分な小規模チームの場合は、ロールバック ソリューションを独自に実装できます。

全体計画は以下のとおりです。

写真

  • プロセス全体のコンテキスト データを保存するには、Context オブジェクトを作成します。ロールバックするタスクのリストを保持する List<RollbackEntry> プロパティがあります。
  • 各転送プロセスが完了すると、リバース コールバックとロールバック タスクがコンテキストに登録されます。
  • もし

重要なトランザクションが正常にコミットされると、コンテキストによって登録された RollbackEntry は無意味になります。

重要なトランザクションがコミットに失敗すると、逆の補償のために Context の fireFallback メソッドが呼び出されます。 fireFallback メソッドは、登録されたロールバック メソッドを逆に呼び出して、業務状態を復元します。

このソリューションはメモリ実装に基づいており、失敗する可能性があります。厳密なシナリオでの使用はお勧めしません。

3.3.再試行可能なトランザクション

再試行可能なトランザクションとは、ネットワークの変動などにより特定の操作が失敗した場合に、その操作を再実行することで期待どおりの結果を得ることができる業務操作を指します。たとえば、テキスト メッセージの確認コードを送信するときに、ネットワークの状態が悪いために送信が失敗した場合は、成功するまで再試行できます。

再試行可能なトランザクションには失敗はなく、成功のみがあります。たとえ短期間の失敗であっても、無制限の再試行により最終的には成功します。

3.3.1. @リトライ

@Retry は、メソッド呼び出しが失敗した場合に自動的に再試行するために Spring フレームワークによって提供されるアノテーションです。

@Retry アノテーションを使用すると、再試行回数、間隔時間、例外タイプなどの情報を定義して、より信頼性の高いメソッド呼び出しを実現できます。

具体的には、@Retry アノテーションは次の属性で構成できます。

  • maxAttempts: 再試行の最大回数。
  • 値: 再試行間隔の数値表現。
  • fixedDelay: 次の試行を行う前に固定の再試行間隔を待機するかどうか。
  • backoffPolicy: 再試行間隔のバックオフ ポリシー。
  • allowCoreThreadTimeOut: コアスレッドでのタイムアウト待機を許可するかどうか。
  • excludeExceptions: 除外する必要がある例外の種類。
  • excludeClassNames: 除外する必要があるクラス名のリスト。
  • loggerMessage: ログ出力形式。
  • fallbackMethodName: すべての再試行が失敗した場合に実行されるメソッド名。

具体的な使い方を見てみましょう。

  • カウンターベースの再試行の実装
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000)) public void doSomething() throws Exception { // 业务逻辑代码}

この実装では、メソッド呼び出しが失敗した場合に最大 3 回再試行し、各再試行の間に 1 秒待機します。 3 回以上の再試行後に失敗した場合は、例外がスローされます。

  • カスタム例外処理に基づく再試行の実装
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000), fallback = @Fallback(fallbackMethod = "doDefault")) public void doSomething() throws Exception { // 业务逻辑代码} private String doDefault(Exception e) { // 当出现指定异常时,执行该方法进行重试处理}

この実装では、メソッド呼び出しが失敗した場合に最大 3 回再試行し、各再試行の間に 1 秒待機します。 3 回以上の再試行後にリクエストが失敗した場合は、再試行するために doDefault メソッドが実行されます。この方法では、例外を処理するための処理をカスタマイズできます。

@Retry は依然としてメモリ ソリューションであり、極端なシナリオではタスクが失われる可能性があります。そのため、実際の業務では、再試行可能なトランザクションなどのシナリオで使用されることはほとんどありません。

3.3.2. MQ

MQ (メッセージ キュー) コンシューマー再試行メカニズムとは、メッセージを消費するときに、コンシューマーがメッセージを正常に消費できない場合 (たとえば、ネットワーク異常、サーバー障害など)、メッセージの信頼性と可用性を確保するために、自動的に一定回数再試行するか、一定の間隔を置いて再度メッセージの消費を試行することを意味します。

次の図に示すように:

私は

MQ を使用した再試行可能なトランザクションには、次の保証が必要です。

  • ビジネス オペレーションと消費者への配信の一貫性を確保します。ビジネス オペレーションが成功した場合、メッセージは正常に配信される必要があります。ビジネスオペレーションが失敗した場合、メッセージは配信できません。
  • メッセージの配信と消費の間の一貫性を確保する: 消費に失敗したメッセージについては、消費が成功するまで MQ が自動的に再試行します。

通常、メッセージ配信とメッセージ消費の間の一貫性を実現するために複数の配信が使用されるため、メッセージ コンシューマーは複数の配信によって生じるビジネス上の問題を回避するためにべき等性を確保する必要があります。

3.3.2.1.セミメッセージ

RocketMQ トランザクション メッセージングは​​、分散トランザクションをサポートするメッセージング モデルです。メッセージの生成と消費をビジネス ロジックにバインドして、メッセージの送信とトランザクション実行の原子性を保証し、メッセージの信頼性を保証します。

トランザクション メッセージは、メッセージの送信とメッセージの確認の 2 つの段階に分かれています。メッセージの確認は、コミットとロールバックの 2 つの操作に分かれています。メッセージはコミット操作が完了した後にのみコンシューマーによって消費され、ロールバック操作が完了した後にメッセージは削除されるため、トランザクションの一貫性と信頼性が実現されます。

トランザクション メッセージ生成のプロセスは次のとおりです。

写真

  • プロデューサーは準備メッセージを RocketMQ サーバーに送信し、RocketMQ サーバーはメッセージをローカルに保存して結果を返します。
  • プロデューサーはローカル トランザクションの実行を開始し、ローカル トランザクションの結果に基づいてステータス情報を RocketMQ サーバーに送信します。
  • ローカル トランザクションが正常に実行された場合、プロデューサーは RocketMQ サーバーにコミット メッセージを送信します。
  • ローカル トランザクションが失敗した場合、プロデューサーは RocketMQ サーバーにロールバック メッセージを送信します。
  • コミットまたはロールバック メッセージを受信すると、RocketMQ はメッセージを配信または削除します。

プロデューサーが準備メッセージを送信したが、指定された時間内にコミットまたはロールバック メッセージを送信しない場合、RocketMQ は次のように回復プロセスに入ります。

写真

  • チェック時間までに対応するコミットまたはロールバックメッセージが受信されない場合、RocketMQ は準備メッセージを再度チェックします。
  • アプリケーションは再チェック指示を受け取り、業務データベースからデータを取得し、業務ロジックに基づいてコミットするかロールバックするかを決定します。
  • コミットまたはロールバック応答を受信した後、RocketMQ はビジネス操作とメッセージ送信間の一貫性を実現するために対応するアクションを実行します。

以下は、RocketMQ を使用したトランザクション メッセージ コードの例です。

 // 编写事务监听器类public class TransactionListenerImpl implements TransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); // 执行本地事务public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { int value = transactionIndex.getAndIncrement(); System.out.println("executeLocalTransaction " + value); // TODO 执行本地事务,并返回事务状态// 本例假定index 为偶数的消息执行成功,奇数的消息执行失败if (value % 2 == 0) { return LocalTransactionState.COMMIT_MESSAGE; } return LocalTransactionState.ROLLBACK_MESSAGE; } // 检查本地事务状态public LocalTransactionState checkLocalTransaction(MessageExt msg) { System.out.println("checkLocalTransaction " + msg.getTransactionId()); // 模拟检查本地事务状态,返回事务状态boolean committed = prepare(true); if (committed) { return LocalTransactionState.COMMIT_MESSAGE; } return LocalTransactionState.UNKNOW; } // 模拟操作预处理逻辑private boolean prepare(boolean commit) { System.out.println("prepare " + (commit ? "commit" : "rollback")); return commit; } } // 编写发送消息的代码public class Producer { private static final String NAME_SERVER_ADDR = "localhost:9876"; public static void main(String[] args) throws Exception { TransactionMQProducer producer = new TransactionMQProducer("MyGroup"); producer.setNamesrvAddr(NAME_SERVER_ADDR); // 注册事务监听器producer.setTransactionListener(new TransactionListenerImpl()); producer.start(); // 发送事务消息String[] tags = {"TagA", "TagB", "TagC"}; for (int i = 0; i < 3; i++) { Message msg = new Message("TopicTest", tags[i], ("Hello RocketMQ " + i).getBytes(StandardCharsets.UTF_8)); // 在消息发送时传递给事务监听器的参数SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); } // 关闭生产者producer.shutdown(); } }

コードだけでは理解しにくいので、次のように簡単な図を描きました。

写真

コア部分は TransactionListener の実装です。残りは基本的に通常のメッセージ送信と同じです。 TransactionListener は主に次の処理を実行します。

  • ローカルトランザクション、つまりビジネスオペレーションを実行します。
  • ビジネス データをチェックして結果検出を実行し、後続のメッセージ処理戦略を決定します。

トランザクション メッセージを使用するには、TransactionListener に多くの適応ロジックを記述する必要があり、研究開発コストが増加します。同時に、ロジックが複数の場所に分割されるため、コードを理解するコストも増加します。

トランザクション メッセージにいくつか問題があります:

  • MQの実装に強く関連しています。すべてのMQ実装がトランザクションメッセージのサポートを提供するわけではありません。
  • APIは比較的不明瞭で、特定の学習コストが必要です。さらに、ビジネスロジックはリスナーに分割する必要があり、理解のコストが増加します。

実用的で使いやすいソリューションがある場合は、トランザクションメッセージテーブルソリューションを使用できます。

3.3.2.2。トランザクションメッセージテーブル

トランザクションメッセージテーブルソリューションは、メッセージの送信と事業運営の一貫性を確保するために一般的に使用される方法です。ソリューションは、データベーストランザクションとメッセージキューに基づいています。メッセージの送信とビジネスオペレーションを同じトランザクションに入れ、データベースメッセージテーブルに事業運営のステータスとメッセージ送信のメッセージを記録して、メッセージの信頼性と慣習を実現します。

次の図に示すように:

写真

コアプロセスは次のとおりです。

  • アプリケーションはデータベーストランザクションを開始し、事業運営を実行し、トランザクションでメッセージを送信します。
  • トランザクションでは、ビジネスオペレーションとメッセージ送信のステータスがメッセージテーブルに記録されます。
  • 事業運営が正常に実行され、メッセージが正常に送信されると、トランザクションがコミットされます。
  • メッセージテーブルを定期的にスキャンし、メッセージステータスに従って未確認のメッセージを再送信します。メッセージが正常に送信された場合、メッセージステータスを更新します。それ以外の場合は、メッセージのステータスを更新するか、再取得の数に従ってメッセージを破棄します。

トランザクションメッセージテーブルソリューションは、メッセージの信頼性と慣習を確保できます。送信メッセージが失敗したり、アプリケーションがクラッシュした場合でも、ビジネス操作とメッセージ送信ステータスをメッセージを控えることで同期することができます。同時に、このソリューションは、メッセージの送信を繰り返し逃したことを避けることができます。

一般的なソリューションとして、LEGOはそれをサポートし、信頼できるメッセージモジュールを参照してください。

4。ビジネス報酬

どのソリューションが設計で使用されていても、矛盾の可能性を減らしようとしていますが、ひどいことは、矛盾の問題が最終的に発生することです。

そんなに多くのことをした後、ルートから一貫性の問題を完全に解決できないのは少し奇妙ではありませんか?実際の作業では、これが事実です。

  • すべての補償可能なトランザクションを正常にロールバックできるわけではありません。フォワードプロセスでは、リソースをロックします。他の操作がロックされたリソースまたはアクセス条件を破壊する場合、プログラムは正常にロールバックできず、問題を解決するために手動介入が必要です。たとえば、注文が配置されたときにクーポンが正常にロックされますが、スーパー管理者は、クーポンが誤って発行されていることを発見し、それを取り戻します。クーポンがロールバックされると、クーポンが利用できない状態にあるため、正常にロールバックすることはできません。
  • すべての再生可能なトランザクションを正常に再試行できるわけではありません。再生可能なトランザクションの実行は、重要なトランザクションの前の条件が満たされることを証明するだけでなく、必ずしも下流の再生可能なトランザクションの条件を満たしているわけではありません。たとえば、支払いが成功した後、WeChatメッセージをユーザーに送信する必要がありますが、ユーザーの承認情報が期限切れになり、メッセージを送信できません。
  • ビジネスイテレーションによって導入されたバグは、トランザクションメカニズムを損傷する可能性があります。これはより一般的です。バグはプロセスエラーを引き起こし、問題とデータを修正する必要があります。

不整合の確率を積極的に減らすことに加えて、多くの場合、ビジネス補償と呼ばれる受動的保護メカニズムを追加することも必要です。

クエリモード

クエリモデルは、最も一般的に使用される方法であり、主にネットワーク伝送の3番目の状態問題を扱うために使用されます。

第三の状態は、分散システムでは、ネットワークの呼び出しを行う際に、呼び出し元の状態が変更されたかどうかを判断できない状況を指します。この場合、第三の状態は未知の状態と見なすことができ、この問題を解決するためにいくつかのメカニズムが必要です。

写真

上の図に示すように、ネットワークコールが第3状態に表示される場合、最も簡単な方法は、不確実な状態を照会することです。

  • 発信者はサービスに電話して事業運営を完了します。実行結果が正常に取得された場合、その後のプロセスは直接実行されます。
  • ネットワークタイムアウトが発生した場合、ステータスクエリインターフェイスを使用して、以前の操作が完了したかどうかを確認します。もし:

完了した場合は、後続のプロセスを続行します。

完了しておらず、サービスコールを再開始します。

RocketMQのトランザクションメッセージは、このメカニズムに基づいて実装されます。

4.2.タスク検出モード

事業運営が完了すると、複数の後続のタスクを処理する必要があります。すべてのタスクが実行されるようにするために、このモードを使用できます。

次の図に示すように:

写真

画像

  • 事業運営後、ビジネスの変更と検出タスクは、同じトランザクション保護の下でデータベースに保存されます。
  • システムは、後続のタスクを実行し続け、完了後にタスクステータスを更新します。
  • システムは、タイムアウトして実行されていないタスクを定期的にロードおよび検出します。

実行されている場合は、タスクステータスを更新します

実行されていない場合は、実行するタスクをトリガーします

ローカルメッセージテーブルは、このモードに基づいて構築されています。

4.3.調整モード

和解モデルは、多くの場合、銀行やその他の金融機関とつながるシナリオに表示されます。

写真

ビジネスの和解のアイデアは非常に簡単です:

  • さまざまなビジネスシステムから和解データを取得します。
  • 規則に従って双方向の調整を実行します。もし

システムが一貫している場合

一貫性がない場合、アラームが発行され、手動介入が必要になります

一方向の調整がデータの損失をもたらすため、調整は双方向でなければなりません。

5. まとめ

一貫性は、分散システムが直面する大きな課題です。さまざまなシナリオによると、一貫性を次のように分割できます。

  • トランザクションの一貫性。トランザクション内のすべての操作は完了または完了していないため、これらの操作がデータの一貫した更新であり、データの矛盾を回避できます。これは、主に、リレーショナルデータベースでの酸トランザクションなどのトランザクション保証を使用することによって達成されます。
  • レプリカの一貫性。各レプリカ間のデータは一貫しています。データが変更されると、変更をすべてのレプリカに同期する必要があります。これは、MySQLマスタースレーブレプリケーションやMySQLがデータの同期などのレプリカ同期テクノロジーを使用して主に実装されています。

この記事では、次のようなトランザクションの一貫性の包括的な説明に焦点を当てています。

  • 技術的な視点、一般的な解決策:

MySQLの実装

2PCおよびXAプロトコル

TCCソリューション

  • ビジネスの観点から見ると、さまざまなトランザクションが分類され、より良い解決が行われます。
  • 重要なビジネス
  • 補償可能なトランザクション
  • 再生可能なトランザクション

これらのソリューションを使用すると、多くのシナリオがさらに追加のビジネスサポートが必要です。一般的なソリューションには次のものがあります。

  • クエリモデル
  • タスクチェックモード
  • 和解モデル

<<:  Kubernetes にアプリケーション (Nginx) をデプロイする方法は 2 つあります。どちらがお好みですか?

>>:  2023 年に信頼できるクラウド コンピューティング サービスのトップ 5

推薦する

webhosting24: 年間 28 ユーロ、日本 1Gbps 帯域幅の高性能 VPS、AMD Ryzen 9 3900X+NVMe

webhosting24は、7月1日から日本のデータセンターのVPSをNVMeにアップグレードし、ハ...

SEOフォーラムが直面するジレンマ

SEO フォーラムは確かにウェブサイトのランキングを管理するのに良い手段です。現状から判断すると、多...

ロングテール理論を最大限に活用する方法!

みなさんこんにちは。私は 2366 ウェブゲーム サイトの SEM スタッフです。私の名前は Hu ...

VPS格安販売業者、最も安いVPS

VPS を使用する顧客はレベルが異なり、目的も多様であるため、一部の友人は VPS に対して特に高い...

servarica: カナダの VPS、4g メモリ/4 コア/200g SSD/無制限トラフィック、ネイティブ IP、Windows をサポート

servarica は非常に古いカナダの VPS ブランドです。主に大容量ハードディスク VPS を...

分散アーキテクチャの進化についてお話ししましょう

1. 分散アーキテクチャとは何ですか?分散システムは、ネットワーク上に構築されたソフトウェア システ...

3つの大きな動きがeLong.comに大きな打撃を与え、行き詰まりに近づいている

eLongの2013年度の財務報告は芳しいものではなかった。純損失は1億6800万元で、2009年に...

2019年第1四半期の世界モバイル市場レポート!

Sensor Tower Store Intelligence のデータによると、2019 年第 1...

Huaban.com、ソーシャル電子商取引サービスへの参入に向けて「Huaban Market」をテスト

国内のPinterestのようなウェブサイトHuaban.comは「Huaban Market」をひ...

過去4年間でオンライン収入についての理解を徐々に深めていった経緯を詳しく説明します

2011年は私が自分でウェブサイトを構築し始めた最初の年でした。インターネットでお金を稼ぐ初心者だっ...

Godaddy - 全製品に36%割引適用

Godaddy は、あらゆる製品で 6.6% の割引が受けられる新しい割引コードをリリースしました。...

hostdare: 15% オフ、US cn2 gia シリーズ VPS、年間 30 ドルから、512M メモリ/1 コア/10g NVMe/250g 帯域幅

Hostdare は現在 15% 割引を提供しています。これは、HDD および NVMe ハード ド...

ウェブサイトのコンテンツの更新とメンテナンスを行い、ランキングが自然に上がるようにすることで、長期的な発展が達成されます。

Baidu 検索エンジンのアルゴリズムは 2012 年初頭に変更され始め、それから 4 か月近く経ち...

タオバオフロントエンドエンジニア:国内ウェブフロントエンド開発10日間

私はずっと、この「Ten Days Talk」を書いて、Web フロントエンド開発における私の経験に...