DUICUO

ZooKeeper の分散トランザクションを理解していますか?

序文

コンビニで何かを買い、支払いをしたのに、店員が他の用事で忙しくて支払いを忘れていて、再度支払いを要求された、そんな状況に遭遇したことはありませんか?あるいは、オンラインで買い物をして、代金は引き落とされたのに、取引は発生していないと表示された、なんて経験ありませんか?こうした状況はすべて、取引が行われていないことに起因しています。これは、人生における取引の重要性を物語っています。取引では、コンビニで何かを買うことは単なるお金と商品の交換です。一方、オンラインショッピングでは、支払いをするとすぐに注文の取引が成立します。

[[282777]]

トランザクション定義

トランザクションは、あるアクティビティに関わるすべての操作を、分割できない実行単位に組み込むメカニズムを提供します。トランザクション内のすべての操作は、すべての操作が正しく実行された場合にのみコミットされます。1つの操作でも失敗すると、トランザクション全体がロールバックされます。簡単に言えば、トランザクションは「すべてかゼロか」のメカニズムを提供します。

ローカルデータベーストランザクション

データベース トランザクションについて説明するときは、データベース トランザクションの 4 つの特性、ACID について言及する必要があります。

A: 原子性

トランザクション内のすべての操作は、完全に完了するか、完全に失敗するかのいずれかであり、中間段階で終了することはありません。トランザクションの実行中にエラーが発生した場合、トランザクションは開始前の状態にロールバックされ、トランザクションが実行されなかったかのようになります。

何かを購入するときと同じように、代金を支払って商品を受け取るか、商品を配送できない場合は返金を受けます。

C: 一貫性

トランザクションの一貫性とは、トランザクションの実行前後でデータベースが一貫した状態にあることを意味します。トランザクションが正常に完了すると、システム内のすべての変更が正しく適用され、システムは有効な状態になります。トランザクション中にエラーが発生した場合、システム内のすべての変更は自動的にロールバックされ、システムは元の状態に戻ります。

I: 孤立

同時実行環境において、複数のトランザクションが同時に同じデータを操作すると、各トランザクションはそれぞれ独自の完全なデータ空間を持ちます。同時実行トランザクションによる変更は、他の同時実行トランザクションによる変更から分離する必要があります。あるトランザクションが更新データを参照する場合、データは別のトランザクションによって変更される前の状態、または別のトランザクションによって変更された後の状態のいずれかであり、中間状態にあるデータは参照しません。

たとえば、何かを買うという行為は他の人に影響を与えません。

D: 耐久性

これは、トランザクションが正常に完了すると、データベースへの更新内容が永続的に保存される必要があることを意味します。システムクラッシュが発生した場合でも、データベースシステムを再起動することで、トランザクションが正常に終了した時点の状態にデータベースを復元できます。

例えば、何かを購入したときは、店主が忘れても記録が残るように台帳に記録する必要があります。

InnoDB 実装の原則

InnoDBはMySQLのストレージエンジンであり、MySQLは多くの人にとって馴染み深いものとなっています。ここでは、データベーストランザクションの実装における基本原則について簡単に説明します。ローカルトランザクションでは、サービスとリソースはトランザクション内の単一のエンティティとして扱うことができます。

ローカル トランザクションはリソース マネージャーによって管理されます。

トランザクションのACID特性は、InnoDBのログとロックによって保証されます。トランザクションの分離性はデータベースのロック機構によって実現され、永続性はREDOログによって、そしてアトミック性と一貫性はUNDOログによって実現されます。

UndoLogの原理はシンプルです。トランザクションのアトミック性を保証するため、データを操作する前に、まずデータを特定の場所にバックアップします(このバックアップデータの保存場所はUndoLogと呼ばれます)。その後、データを変更します。エラーが発生した場合、またはユーザーがROLLBACKステートメントを実行した場合、システムはUndoLogのバックアップを使用して、トランザクション開始前の状態にデータを復元できます。

Undoログとは異なり、Redoログは新しいデータのバックアップを記録します。トランザクションがコミットされる前は、Redoログのみを永続化すればよく、データ自体は永続化する必要はありません。システムがクラッシュした場合、データは永続化されませんが、Redoログは永続化されます。システムはRedoログの内容に基づいて、すべてのデータを最新の状態に復元できます。具体的な実装プロセスに興味のある方は、詳細情報を検索してください。

分散トランザクション

分散トランザクションとは何ですか?

分散トランザクションとは、参加者、サポートサーバー、リソースサーバー、トランザクションマネージャーが分散システム内の異なるノードに存在するトランザクションを指します。簡単に言えば、大規模な操作は、異なるサーバーに分散され、異なるアプリケーションに属する小さな操作で構成されています。分散トランザクションは、これらの小さな操作がすべて成功するか、すべて失敗するかのいずれかを保証する必要があります。本質的に、分散トランザクションは、異なるデータベース間でデータの一貫性を保証することを目的としています。

分散トランザクションの出現の理由

上記のローカル トランザクションは 2 つの部分として考えることができます。1 つはサービスが複数のノードを生成することであり、もう 1 つはリソースが複数のノードを生成することです。

複数のノードにサービスを提供する

インターネットの急速な発展に伴い、マイクロサービスやSOAといったサービスアーキテクチャパターンが大規模に活用されるようになりました。例えば、企業内では、ユーザーの資産が残高、ポイント、クーポンなど複数の部分に分割されている場合があります。社内では、ポイント機能は1つのマイクロサービスチームによって管理され、クーポン機能は別のチームによって管理されているといった状況が考えられます。

つまり、ポイントが差し引かれた後にクーポンが正常に適用される保証はありません。

リソース複数ノード

同様に、インターネットの発展はあまりにも速いです。一般的に、数千万件のレコードを処理するには、MySQLデータベースをシャーディングおよびパーティション分割する必要があります。Alipay送金ビジネスの場合、友人に送金する場合、あなたのデータベースは北京にあり、友人のお金は上海に保管されている可能性があります。そのため、両方の取引が同時に成功するという保証はまだありません。

分散トランザクションの基盤

上述のように、分散トランザクションはインターネットの急速な発展に応じて生まれたものであり、これは必然的なものでした。以前、データベースの4つのACID特性について議論しましたが、もはや分散トランザクションには不十分でした。この時点で、一部の専門家が新たな理論を提唱しました。

キャップ

CAP 定理はブリューワーの定理とも呼ばれ、分散システム (分散トランザクションだけではない) を設計する設計者にとって基本的な理論です。

C (一貫性): 特定のクライアントにおいて、読み取り操作は最新の書き込み操作を返す必要があります。複数のノードに分散されたデータの場合、あるノードでデータが更新され、他のすべてのノードがその最新データを読み取ることができる場合、それは強い一貫性と呼ばれます。いずれかのノードで更新されたデータの読み取りに失敗した場合、それは分散不整合と呼ばれます。

可用性 (A): 障害のないノードは、妥当な時間内に妥当な応答(エラー応答やタイムアウト応答ではない)を返します。可用性の重要な2つの側面は、妥当な時間と妥当な応答です。妥当な時間とは、リクエストが無期限にブロックされず、妥当な時間内に応答が返されることを意味します。妥当な応答とは、システムが明確な結果を返すこと、そしてその結果が正しいことを意味します。例えば、40ではなく50を返すなどです。

P(分断耐性):ネットワーク分断が発生した後もシステムが動作を継続できる能力。例えば、複数のマシンで構成されるクラスターでは、1台のマシンにネットワーク障害が発生しても、クラスター全体は正常に動作し続けることができます。

CAPに詳しい方は、これら3つは共有できないことをご存知でしょう。ご興味があれば、CAPの証明を検索してみてください。分散システムでは、ネットワークは100%信頼できるものではなく、分断は避けられない現象です。CAを選択し、Pを放棄した場合、分断が発生した際に一貫性を保つためにリクエストを拒否しなければなりません。しかし、Aはこれを認めていません。したがって、理論上、分散システムはCAアーキテクチャを選択できず、CPまたはAPアーキテクチャしか選択できません。

CP では、一貫性とパーティション耐性を追求するために可用性を犠牲にしていますが、ZooKeeper は実際には強力な一貫性を追求しています。

AP の場合、分断耐性と可用性を追求するために一貫性 (ここでの一貫性とは強い一貫性を指します) を犠牲にすることは、多くの分散システムの設計で行われる選択であり、その後の BASE も AP に基づく拡張です。

ちなみに、CAP定理はネットワーク遅延を無視します。つまり、トランザクションがコミットされると、ノードAからノードBに複製されます。しかし、現実にはこれは明らかに不可能なので、必ず不整合が発生します。また、CAPにおいてCPなどの2つの要素を選択したとしても、Aを放棄すべきではありません。Pが発生する確率は極めて低いため、ほとんどの場合CAを確保する必要があります。たとえパーティションが発生したとしても、他のマシンが復旧して使用可能になるように、例えばログ記録などの対策を講じるなど、後続のAに備える必要があります。

ベース

BASEは、Basically Available(基本的に利用可能)、Soft State(ソフトステート)、そしてEventually Consistent(最終的に一貫性がある)の頭文字をとったものです。CAPにおけるAPの拡張版です。

基本的な可用性: 障害が発生した場合、分散システムは利用可能な機能の一部を失う可能性がありますが、コア機能は引き続き利用可能である必要があります。

ソフト状態: システムの可用性に影響を与えないシステムの中間状態を許可します。これは、CAP の不整合を指します。

最終的な一貫性: 最終的な一貫性とは、一定期間が経過すると、すべてのノード データが一貫性を保つことを意味します。

BASEは、CAPにおける理論的なネットワーク遅延の不在を、ソフトステートと結果整合性を用いて遅延後の一貫性を保証することで解決します。BASEはACIDとは正反対であり、ACIDの強い一貫性モデルとは全く異なります。その代わりに、BASEは可用性のために強い一貫性を犠牲にし、一定期間データの不整合を許容しますが、最終的には一貫性のある状態に到達します。

分散トランザクションソリューション

上記の理論的基礎を踏まえて、分散トランザクションの一般的なソリューションをいくつか紹介します。

分散トランザクションは本当に必要ですか?

ソリューションについて議論する前に、分散トランザクションが本当に必要かどうかをまず明確にする必要があります。

前述のように、分散トランザクションの出現には2つの理由があります。1つはマイクロサービスの過剰な数です。1人の担当者が複数のマイクロサービスを保守しているチームや、過剰な設計によって全員が疲弊しているチームを数多く見てきました。マイクロサービスの過剰な数は、必然的に分散トランザクションにつながります。この場合、以下の解決策はいずれも推奨しません。代わりに、トランザクションを必要とするマイクロサービスを単一マシンサービスに集約し、ローカルデータベーストランザクションを使用してください。いずれの解決策もシステムの複雑さを増大させ、コストが高すぎるためです。特定の設計機能を追求するあまり、不必要なコストと複雑さを導入しないでください。

分散トランザクションを導入する必要があると判断した場合は、次の一般的なソリューションを検討してください。

2PC

2PC について議論するときは、分散データベース トランザクションにおける XA トランザクションについて話す必要があります。

XA プロトコルは 2 つのフェーズに分かれています。

フェーズ 1: トランザクション マネージャーは、トランザクションに関係する各データベースに操作の事前コミットを要求し、コミットできるかどうかを反映します。

フェーズ 2: トランザクション コーディネーターは、各データベースにデータのコミットまたはロールバックを要求します。

利点:可能な限り強力なデータ整合性を確保し、実装コストが低く、主要な主流データベースに独自の実装が存在します。MySQLでは、バージョン5.5以降でサポートされています。

欠点:

  • 単一障害点:トランザクションマネージャはプロセス全体において重要な役割を果たします。例えば、最初のフェーズが完了し、2番目のフェーズがコミットされようとしているときにトランザクションマネージャがクラッシュすると、リソースマネージャは無期限にブロックされ、データベースが使用できなくなります。
  • 同期ブロッキング: リソースの準備が整うと、コミットが完了してリソースが解放されるまで、リソース マネージャー内でブロックされたままになります。
  • データの不整合:2フェーズコミットプロトコルは分散データの強力な一貫性を実現するように設計されていますが、それでもデータの不整合が発生する可能性があります。例えば、第2フェーズでコーディネータがトランザクションのコミット通知を送信したとします。しかし、ネットワークの問題により、通知は一部の参加者にしか受信されず、コミット操作は実行されません。残りの参加者は通知を受信して​​いないため、処理がブロックされ、データの不整合が発生します。

要約すると、XA プロトコルは比較的シンプルで低コストですが、単一障害点と高い同時実行性をサポートできないこと (同期ブロッキングによる) が依然として最大の弱点となっています。

TCC

TCC(Try-Confirm-Cancel)の概念は、2007年にPat Helland氏が発表した論文「分散トランザクションを超えた人生:異端者の意見」で初めて提案されました。XAと比較すると、上記のトランザクションメカニズムはXAのいくつかの欠点を解消しています。

1. コーディネータの単一障害点が解消され、メインのビジネスエンティティがビジネスアクティビティを開始および完了します。ビジネスアクティビティマネージャもマルチポイントとなり、クラスタ化されました。2. 同期ブロッキング:タイムアウトが導入され、タイムアウト後に補償が提供され、リソース全体がロックされることはありません。リソースはビジネスロジックに変換され、より細かい粒度が可能になります。

3. データの一貫性: 補正メカニズムが導入されているため、一貫性はビジネス アクティビティ マネージャーによって制御されます。

TCC の説明:

  • 試行フェーズ: 実行を試行し、すべてのビジネス チェックを完了し (一貫性)、必要なビジネス リソースを予約します (準分離)。
  • 確認フェーズ:このフェーズでは、ビジネスロジックの実際の実行を確認します。ビジネスチェックは実行されず、試行フェーズで確保されたビジネスリソースのみが使用されます。確認操作はべき等である必要があります。べき等設計が必須であり、確認が失敗した場合は再試行が必要です。
  • キャンセルフェーズ:実行をキャンセルし、Tryフェーズで確保されたビジネスリソースを解放します。キャンセル操作は冪等性を満たします。キャンセルフェーズにおける例外処理スキームは、基本的にConfirmフェーズと同じです。

簡単な例を挙げると、100元の水を1本買う場合、トライ段階では財布の中に100元が十分にあるか確認し、鍵をかける必要があります。水についても同じことが言えます。

どちらかが失敗した場合はキャンセルする(100元と水のボトルを解放する)。失敗の有無にかかわらずキャンセルが失敗した場合は、キャンセルを再試行する。したがって、冪等性は維持される必要がある。

すべての手順が成功した場合、100元の減額とこのボトル入り飲料水の売上を確定します。確定に失敗した場合は、失敗の理由に関わらず、再試行してください(再試行はアクティビティログに基づいて行われます)。

いくつかは TCC に適しています:

  • 強力な分離性と厳格な一貫性を必要とするアクティビティ。
  • 実行時間が短いビジネス

このソリューションの中核は、メッセージログを介して分散処理を必要とするタスクを非同期的に実行することです。これらのメッセージログは、ローカルテキストファイル、データベース、またはメッセージキューに保存でき、ビジネスルールに基づいて自動または手動で再試行を開始できます。手動再試行は、決済シナリオでより一般的に使用され、イベント発生後の問題を照合システムを通じて処理します。

ローカルメッセージキューの核となるのは、大規模なトランザクションを小規模なトランザクションに変換することです。100元でボトル入りの水を購入する例をもう一度考えてみましょう。

1. 料金を差し引く場合、差し引くサーバーに新しいローカルメッセージテーブルを追加する必要があります。料金の差し引きと水在庫のローカルメッセージテーブルへの書き込みを同じトランザクションで実行する必要があります(一貫性を保つために、データベースのローカルトランザクションを使用します)。

2. この時点で、スケジュールされたタスクがローカルのトランザクションテーブルをポーリングし、未送信のメッセージを製品在庫サーバーに送信し、水在庫を減算するよう指示します。メッセージが製品サーバーに到達すると、まずサーバーのトランザクションテーブルに書き込まれ、その後減算が実行されます。減算が成功すると、トランザクションテーブルのステータスが更新されます。

3. 製品サーバーは、スケジュールされたタスクを介してメッセージ テーブルをスキャンするか、支払いサーバーに直接通知し、支払いサーバーはローカル メッセージ テーブルのステータスを更新します。

4. 何らかの異常事態が発生した場合、未処理のメッセージは定期的にスキャンされ、再送信されます。製品サーバーはメッセージを受信すると、まず重複がないか確認します。重複している場合は、実行するかどうかを確認します。実行する場合は、直ちにトランザクションに通知します。実行されない場合は、再実行する必要があります。ビジネスでは、余分な水のボトルを差し引かないように、冪等性を確保する必要があります。

ローカルメッセージキューはBASE理論に準拠し、結果整合性モデルを採用しています。これは、整合性要件が低いアプリケーションに適しています。このモデルを実装する際には、再試行メカニズムにおける冪等性を確保することが不可欠です。

MQトランザクション

RocketMQは分散トランザクションを実装しています。これは本質的にはローカルメッセージテーブルをラップし、それをMQ内部に移動するものです。以下はMQトランザクションの簡単な紹介です。より詳細な情報については、www.jianshu.com/p/453c6e7ff… をご覧ください。

基本的なプロセスは次のとおりです。最初の段階では、準備されたメッセージがメッセージ アドレスを取得します。

2 番目のフェーズでは、ローカル トランザクションを実行します。

第3段階では、第1段階で取得したアドレスを使用してメッセージにアクセスし、その状態を変更します。その後、メッセージの受信者はメッセージを使用できるようになります。

メッセージの確認に失敗した場合、RocketMQ Brokerは、ステータスが更新されていないメッセージを定期的にスキャンします。メッセージが確認されていない場合、コミットするかどうかを決定するために、送信者にメッセージが送信されます。RocketMQでは、これは処理のためのリスナーとして送信者に提供されます。

消費がタイムアウトした場合、無期限に再試行する必要があり、メッセージ受信者は冪等性を保証する必要があります。メッセージの消費が失敗した場合、その確率は低いため、手動で処理する必要があります。このような低確率のイベントに対して、これほど複雑なプロセスを設計することは逆効果です。

佐賀問題

Sagaは、30年前にデータベース倫理に関する論文で言及された概念です。その中核となる考え方は、長いトランザクションを複数のローカルな短いトランザクションに分割し、Sagaトランザクションコーディネータによって調整することです。トランザクションが正常に完了した場合は正常に完了し、ステップが失敗した場合は逆順に補償操作が呼び出されます。Sagaの構成要素は以下のとおりです。

各Sagaは、一連のサブトランザクションTiで構成されています。各Tiには対応する補償アクションCiがあり、これはTiによって発生した結果を元に戻すために使用されます。ここでの各Tはローカルトランザクションです。ご覧のとおり、TCCと比較すると、Sagaには「予約された試行」アクションがなく、Tiはデータベースに直接コミットされます。

Saga には 2 つの実行順序があります。

T1、T2、T3、...、Tn

T1、T2、...、Tj、Cj、...、C2、C1(0 < j < n)。Sagaは2つの回復戦略を定義しています。

バックワードリカバリ(前述の2番目の実行順序とも呼ばれます)では、jは失敗したサブトランザクションを表します。これは、それ以前の成功したサブトランザクションをすべて元に戻し、Saga全体の実行結果を元に戻します。フォワードリカバリは、成功が必須のシナリオに適しており、実行順序はT1、T2、…、Tj(失敗)、Tj(再試行)、…、Tnのようになります。jは失敗したサブトランザクションを表します。この場合、Ciは必要ありません。

リソースがロックされず、他のトランザクションが現在のトランザクションを上書きしたり影響を与えたりする可能性があるため、サガ パターンでは分離を保証できないことに注意することが重要です。

100元でボトル入りの水を購入する例を考えてみましょう。ここでは…と定義します。

T1 = 100 元を減額します。T2 = ユーザーに水のボトルを 1 本追加します。T3 = 在庫を水のボトル ​​1 本分減らします。

C1 = 100 元を追加します。C2 = ユーザーの水分摂取量を 1 本減らします。C3 = 在庫に水を 1 本追加します。

T1、T2、T3 の各操作中に問題が発生した場合、問題となっている操作 C の逆操作が実行されます。前述の分離性の問題は、T3 のロールバックが必要な状況で、ユーザーが既に(別のトランザクションで)水を飲んでいる場合に発生します。ロールバック中は、ユーザーのアカウントから水を差し引くことができなくなります。これは、トランザクション間の分離性の欠如による問題です。

Sagaパターンの分離性の欠如が重大な影響を及ぼすことは明らかです。Huaweiのソリューションを参考に、ビジネスレベルでセッションとロックメカニズムを実装し、リソース操作のシリアル化を確保しましょう。あるいは、資金を事前に凍結することで、これらのリソースをビジネスレベルで分離することも可能です。そして、ビジネスオペレーション中は、現在の状態を迅速に読み取ることで、最新のアップデートを取得できます。

やっと

繰り返しになりますが、絶対に必要な場合を除き、分散トランザクションの使用は避けてください。どうしても使用する必要がある場合は、ビジネスニーズを分析し、強力な一貫性を優先するか、結果整合性を優先するかなど、どちらのアプローチが最適かを判断してください。上記で概説したソリューションは、あくまでも簡略化した概要です。これらのソリューションを効果的に実装するには、それぞれのアプローチを慎重に検討する必要があり、非常に複雑になります。したがって、分散トランザクションを使用するかどうかを徹底的に評価することの重要性を改めて強調しておきます。