この記事はWeChatの公開アカウント「三太子敖冰」から転載したもので、著者は三太子敖冰です。記事を転載する場合は、第三王子敖冰の公式アカウントまでご連絡ください。 序文 前回の記事では配布についてすでに説明しました。温かい人が分散トランザクションについて話すと言ったなら、彼は間違いなくそれについて話すでしょう。でも、ウォームマンがこんなに早く終わるとは思っていなかったでしょうね? トランザクションについては誰もがよく知っていると思いますが、ACID が何であるかについては、これもまた決まり文句です。ただし、記事の完全性を確保し、誰もが理解できるようにするには、まず ACID について説明し、次に分散トランザクションとは何か、2PC、3PC、TCC、ローカル メッセージ テーブル、メッセージ トランザクション、ベスト エフォート通知などの一般的な分散トランザクションを紹介する必要があります。 取引 厳密に言えば、トランザクションの実装には、ACID と呼ばれる原子性、一貫性、独立性、耐久性が必要です。
一般的な意味では、トランザクションは、一部の更新操作が成功または失敗することを保証するように設計されています。 この時点で、それは正しくない、Redis トランザクションはすべての操作が実行される、または実行されないことを保証できないのに、なぜトランザクションとも呼ばれるのか、と言う人もいるかもしれません。 まず、一般的なミドルウェアはその効果を誇張することを知っておく必要があります。チームももっと有名になって、もっと多くの人に製品を使ってもらいたいと考えているので、弁証法的に考えなければなりません。 一般的に言えば、何かを達成したと敢えて言う場合、それは実際にそれを達成したか、またはその機能が何らかの特別な、カスタマイズされた、または極端な条件下でのみ達成可能であることを意味します。 Redis が何を言っているか見てみましょう。 この文は、トランザクション内のコマンドが失敗した場合でも、後続のコマンドは引き続き処理され、Redis はコマンドを停止しない、つまりロールバックされないことを示しています。 これはナンセンスだと思いませんか?これは問題の本質的な目的から逸脱しています。 焦らずに、Redis がどのように説明するか見てみましょう。 Redis の公式 Web サイトでは、ロールバックがサポートされない理由が説明されています。まず、コマンドが間違っている場合、それは構文エラーであり、あなた自身のプログラミングエラーであり、この状況は開発中に検出されるべきであり、実稼働環境では発生しないはずであると述べました。 そして、Redis は高速であることに特化しています。ロールバックを提供する必要はありません。 以下にスクリーンショットを撮らない別の段落がありますが、これは、ロールバックが提供されていても、コードが誤って記述されているため役に立たず、ロールバックではプログラミング エラーからあなたを救うことができないことを意味します。また、通常、この種のエラーが本番環境に侵入する可能性は低いため、よりシンプルで高速な方法を選択してください。ロールバックはサポートされていません。 ほら、これは理にかなっているように思えます。プログラミングミスの代償を支払う必要がないため、ロールバックは提供していません。 しかし、何かがおかしいようです?それは角度と立場によって異なります。あなた自身で判断してください。 次に、分散トランザクションを開始します。 分散トランザクション 名前が示すように、分散トランザクションは分散システムで実装されます。それらは実際には複数のローカル トランザクションで構成されています。 分散トランザクションでは ACID が満たされることはほとんどありません。実際、ほとんどの場合、スタンドアロン トランザクションでは ACID が満たされません。そうでなければ、どうして 4 つの分離レベルが存在するのでしょうか?したがって、異なるデータベースや異なるアプリケーションに分散された分散トランザクションは言うまでもありません。 まずは2PCを見てみましょう。 2PC 2PC (2 フェーズ コミット プロトコル)、中国語では 2 フェーズ コミットと呼ばれます。 2 フェーズ コミットは強力な一貫性設計です。 2PC は、各参加者 (各ローカル リソースとも呼ばれます) のコミットとロールバックを調整および管理するトランザクション コーディネーターの役割を導入します。 2 つのフェーズは、準備 (投票) フェーズと提出フェーズを指します。 これは単なる合意または理論的なガイダンスであり、一般的な方向性を説明するだけであることに注意してください。具体的な実装には依然として違いがあります。 次の2つの段階の具体的なプロセスを見てみましょう。 準備フェーズでは、コーディネーターが各参加者に準備コマンドを送信します。トランザクションのコミット以外のすべてが実行されるため、準備コマンドを理解できます。 すべてのリソースからの応答を同期的に待機した後、2 番目のフェーズであるコミット フェーズが開始されます (コミット フェーズは必ずしもトランザクションのコミットを意味するわけではなく、トランザクションのロールバックを意味する場合もあることに注意してください)。 最初のフェーズですべての参加者が正常な準備を返す場合、コーディネーターはすべての参加者にトランザクションのコミット コマンドを送信し、すべてのトランザクションが正常にコミットされるまで待機してから、トランザクションの実行が正常であることを返します。 フローチャートを見てみましょう。 最初のフェーズで 1 人の参加者が失敗を返した場合、コーディネーターはすべての参加者にトランザクションをロールバックする要求を送信し、分散トランザクションの実行が失敗したことを示します。 では、第 2 段階の提出が失敗したらどうなるのかと疑問に思う人もいるかもしれません。 ここでは2つの状況があります。 1 つ目は、2 番目のフェーズでロールバック トランザクション操作が実行されることです。この場合の答えは、すべての参加者がロールバックするまで再試行を続けることです。そうしないと、最初のフェーズで正常に準備された参加者がブロックされます。 2 つ目は、第 2 ステージでトランザクション送信操作が実行され、一部の参加者のトランザクションが正常に送信されている可能性があるため、再試行を続けることです。現時点では、急いで前進し、送信が成功するまで再試行し続けるという唯一の方法があります。結局、本当に機能しない場合は、手動による介入が必要になります。 これは一般的に第 2 フェーズの提出のプロセスです。詳細を見てみましょう。 まず、2PC は同期ブロッキング プロトコルです。たとえば、最初のフェーズでは、コーディネーターは次のステップに進む前に、すべての参加者が応答するのを待ちます。もちろん、第 1 フェーズのコーディネーターにはタイムアウト メカニズムがあります。参加者の応答が受信されない場合、またはネットワーク上の理由により参加者が失敗した場合、タイムアウト後にトランザクションは失敗したと判断され、すべての参加者にロールバックコマンドが送信されます。 2 番目のフェーズでは、上記の分析によれば再試行を続けることしかできないため、コーディネーターはタイムアウトできません。 コーディネータ障害分析 コーディネーターは単一のポイントであり、単一障害点の問題があります。 コーディネーターが準備コマンドを送信する前にハングし、トランザクションがまだ開始されていないと想定します。 コーディネーターが準備コマンドを送信した後に切断した場合、一部の参加者はトランザクション リソースがロックされた状態で実行しているのと同じになるため、これは良いことではありません。トランザクションの実行が失敗するだけでなく、一部のパブリック リソースがロックされるため、他のシステム操作もブロックされます。 ロールバック トランザクション コマンドを送信する前にコーディネータがハングした場合、トランザクションは実行できず、最初のステージで成功する準備ができている参加者はブロックされます。 ロールバック トランザクション コマンドを送信した後にコーディネーターがハングしたとします。これは問題ありません。少なくともコマンドは送信されており、ロールバックが成功してリソースが解放される可能性が高いからです。ただし、ネットワークパーティションが発生すると、一部の参加者はコマンドを受信できずにブロックされます。 コミットトランザクションコマンドを送信する前にコーディネーターがハングしたとします。これはうまくいきません。これはバカげている!現在、すべてのリソースがブロックされています。 トランザクション コミット コマンドを送信した後にコーディネーターがハングしたとします。これはまだ問題ありません。少なくともコマンドは送信されており、正常にコミットされてリソースが解放される可能性が高いためです。ただし、ネットワークパーティションの問題が発生すると、一部の参加者はコマンドを受信できずにブロックされます。 コーディネーターが失敗し、新しいコーディネーターが選出される コーディネータの単一ポイント問題のため、選挙などの操作を通じて、新しいコーディネータを選択して置き換えることができます。 最初の段階であれば、影響は実際には大きくないので、ロールバックするだけで済みます。最初の段階では、トランザクションは確実にコミットされていません。 第 2 段階の場合、すべての参加者が生きていると仮定すると、新しいコーディネーターはすべての参加者と自分の状況を確認して、次のステップを推測できます。 参加者の一人が失敗したとします。これは少し堅いです。たとえば、コーディネーターがロールバック コマンドを送信し、最初の参加者がそれを受信して実行すると、コーディネーターと最初の参加者の両方が失敗します。 この時点では、他の参加者はまだリクエストを受け取っておらず、その後、新しいコーディネーターが来ます。他の参加者に尋ねると、全員が「OK」と答えますが、ハングした参加者が大丈夫かどうかはわかりません。これは愚かなことです。 問題は、各参加者とコーディネータだけが自分の状態を知っているため、新しいコーディネータは、出席している参加者の状態から失敗した参加者の状況を推測できないという点にあります。 プロトコルではこれについて言及されていませんが、コーディネータが送信したリクエストを記録、つまりログに記録することを柔軟に許可できます。これにより、新しいコーディネータが来たときに、リクエストを送信するかどうかがわかります。 しかし、コーディネーターがコミット要求を送信する必要があることを知っていたとしても、参加者も電話を切った場合は役に立ちません。参加者が電話を切る前にトランザクションをコミットしたかどうかがわからないためです。 参加者が切断前にトランザクションを正常にコミットし、新しいコーディネータが残りの参加者に問題がないと判断した場合、新しいコーディネータはデータの一貫性を確保するために他の参加者にトランザクションのコミット コマンドを送信する必要があります。 参加者が切断する前にトランザクションが正常にコミットされなかった場合、参加者が回復した後にデータはロールバックされます。このとき、コーディネータはトランザクションの一貫性を維持するために、他の参加者にロールバック トランザクション コマンドを送信する必要があります。 したがって、極端な場合にはデータの不整合を避けることはできません。 口先だけでは済まない。もう一度コードを見てみましょう。もっと明確になるかもしれません。次のコードは <> から取得されます。 このコードは 2PC を実装していますが、2PC と比較すると、ログを書き込むアクションが追加され、参加者は互いに通知し、参加者はタイムアウトも実装します。ここで注意すべき点は、一般的に言われる 2PC には上記の機能は含まれておらず、実装時に追加されるということです。
これまで2PCのさまざまな詳細を詳細に分析してきましたが、まとめてみましょう。 2PC は強力な一貫性を確保しようとする分散トランザクションであるため、同期的にブロックされ、長期的なリソース ロックの問題が発生します。全体的に、非効率であり、単一点障害の問題があります。極端な状況では、データの不整合が発生するリスクがあります。 もちろん、具体的な実装は変更可能であり、2PC には Tree 2PC や Dynamic 2PC などのバリエーションもあります。 皆さんが気づいているかどうか分かりませんが、もう一つの点があります。 2PC はデータベース レベルでの分散トランザクション シナリオに適していますが、ビジネス ニーズはデータベースだけでなく、画像のアップロードやテキスト メッセージの送信も含まれる場合があります。 さらに、Java の JTA では、1 つのアプリケーションで複数のデータベースの分散トランザクションの問題しか解決できず、サービス間で使用することはできません。 Java の JTA について簡単に説明しましょう。 XA 仕様に基づいて実装されたトランザクション インターフェースです。ここで、XA は、データベースの XA 仕様に基づいて実装された 2PC として簡単に理解できます。 (XA 仕様が何であるかについては、スペースが限られているため、次回機会があればお話しします) 次に3PCを見てみましょう。 3PC 3PC は 2PC のいくつかの問題を解決するために作成されました。 2PC と比較すると、参加者間のタイムアウト メカニズムも導入され、参加者がそれぞれの状態を統合するために使用できる新しいステージが追加されます。 詳しく見てみましょう。 3PC には、準備段階、事前コミット段階、コミット段階の 3 つの段階があり、英語では CanCommit、PreCommit、DoCommit に相当します。 2PC の提出フェーズは、提出前フェーズと提出フェーズに分かれているようですが、3PC の準備フェーズでは、コーディネーターは参加者に「今大丈夫ですか?」など、自分の状態について尋ねるだけです。 「荷物は重いですか?」等々。 事前コミット フェーズは 2PC の準備フェーズと同じですが、必要なすべての作業はトランザクションがコミットされる前に実行される点が異なります。 コミットフェーズは 2PC と同じです。図を見てみましょう。 どの段階で参加者が失敗を返しても、トランザクションは失敗と宣言されます。これは 2PC と同じです (もちろん、最終送信段階では、2PC と同様に、送信要求が行われる限り、継続的に再試行することしかできません)。 まず、3PC の位相変化の影響を見てみましょう。 まず、準備フェーズでの変更では、トランザクションを直接実行するのではなく、この時点で参加者にトランザクションを受け入れる資格があるかどうかを最初に尋ねます。したがって、作業を開始するときにリソースが直接ロックされることはなく、特定のリソースが利用できない場合はすべての参加者がブロックされることはありません。 事前提出段階の導入は統一された状態の役割を果たします。これはフェンスのようなもので、事前提出段階の前にすべての参加者が応答していないことを示し、事前処理段階ではすべての参加者が応答したことを示します。 参加者であり、自分が送信前状態に入ったことがわかっている場合は、他の参加者も送信前状態に入ったと推測できます。 ただし、ステージを 1 つ追加すると、インタラクションも 1 つ増えるため、パフォーマンスが低下します。また、ほとんどの場合、リソースは使用可能であるはずなので、リソースが使用可能であることがわかったら、そのたびに 1 回問い合わせる必要があります。 参加者のタイムアウトの影響を見てみましょう。 2PC が同期的にブロックされていることがわかっています。上記で分析したところ、最も損害が大きいのは、送信要求が送信される前にコーディネータがハングし、すべての参加者がリソースをロックして待機状態になることだということです。 次に、タイムアウト メカニズムを導入すると、参加者は無駄に待つことがなくなります。コミット コマンドがタイムアウトするのを待機している場合は、この段階でコミットされる可能性が高いため、トランザクションをコミットします。事前コミット コマンドがタイムアウトするのを待っている場合は、とにかく何もしていないので、必要なことだけを実行します。 ただし、タイムアウト メカニズムによってデータの不整合の問題が発生する可能性もあります。たとえば、コミット コマンドを待機中にタイムアウトが発生した場合、参加者はデフォルトでコミット トランザクション操作を実行しますが、代わりにロールバック操作を実行し、データの不整合が発生する可能性があります。 もちろん、3PC コーディネーターのタイムアウトは依然として存在し、2PC と同じであるため詳細に分析しません。 Wikipedia によると、3PC は、2PC コーディネーターと参加者の両方が送信フェーズで失敗した後、新しく選出されたコーディネーターがコミットするかロールバックするかがわからないという問題を解決するために導入されました。 新しいコーディネータが来て、参加者が事前提出または提出段階にあることがわかった場合、それはすべての参加者によって確認されたことを意味するため、この時点で提出コマンドが実行されます。 そのため、3PC は事前提出フェーズを導入して参加者のステータスを統一します。つまり、全員が同期するためのフェーズが残されることになります。 ただし、これによってコーディネーターは何をすべきかを知ることができるだけで、それが正しいことを保証するものではありません。これは実際には上記の 2PC 分析と一致しています。これは、ハングした参加者がトランザクションを実行したかどうかを判断することが不可能であるためです。 したがって、3PC は、事前コミット フェーズを通じて障害回復の複雑さを軽減できますが、障害が発生した参加者が回復しない限り、データの一貫性を保証することはできません。 まとめてみましょう。 3PC は 2PC に比べていくつかの改善が加えられています。参加者のタイムアウト メカニズムが導入され、障害回復後のコーディネータの決定の複雑さを軽減するために事前コミット フェーズが追加されました。ただし、全体的な対話プロセスは長くなり、パフォーマンスは低下し、データの不整合の問題は依然として存在します。 したがって、2PC も 3PC もデータの 100% の一貫性を保証することはできないため、通常は時間指定スキャン補正メカニズムが必要になります。 3PCについてお話しましょう。具体的な実装はまだ見つかっていないので、3PC は純粋に理論的なものにすぎないと思います。そして、2PC と比較すると、多少の努力はしているものの、その効果はごくわずかであることがわかりますので、それを理解してください。 TCC 2PC と 3PC はどちらもデータベース レベルですが、TCC はビジネス レベルの分散トランザクションです。前にも述べたように、分散トランザクションにはデータベース操作だけでなく、テキスト メッセージの送信なども含まれます。ここで TCC が役立ちます。 TCC は Try - Confirm - Cancel の略です。
実際、その考え方は 2PC に似ています。どちらもまずは暫定的に実行されます。すべてがうまくいけば、実際に実行されます。そうでない場合はロールバックされます。 たとえば、トランザクションで 3 つの操作 A、B、C を実行する必要がある場合、最初に 3 つの操作に対して予約されたアクションが実行されます。すべての予約が成功した場合、確認操作が実行されます。 1 つの予約が失敗した場合は、キャンセル操作が実行されます。 プロセスを見てみましょう。 TCC モデルにはトランザクション マネージャ ロールもあり、これは TCC グローバル トランザクション ステータスを記録し、トランザクションをコミットまたはロールバックするために使用されます。 プロセスが非常に簡単であることがわかります。難しいのはビジネスの定義です。各操作に対して、試行 - 確認 - キャンセルに対応する 3 つのアクションを定義する必要があります。 したがって、TCC はビジネスへの侵入が大きく、ビジネスと密接に結合しており、対応する操作は特定のシナリオとビジネス ロジックに従って設計する必要があります。 もう一つの注意点は、元に戻す操作と確認操作の実行を再試行する必要がある場合があるため、操作のべき等性も保証する必要があることです。 2PCや3PCと比較すると、TCCは適用範囲が広いですが、開発の作業量も大きくなります。結局のところ、これらはすべてビジネスで実装されており、これら 3 つの方法を書くのは本当に難しいことに気付くことがあります。ただし、TCC はビジネスに実装されているため、データベースやさまざまなビジネス システム間でトランザクションを実装できます。 ローカルメッセージテーブル ローカル メッセージ テーブルは、実際には各システムのローカル トランザクションを使用して分散トランザクションを実装します。 名前が示すように、ローカル メッセージ テーブルはローカル メッセージを格納するためのテーブルです。通常はデータベースに配置されます。業務を実行する場合、業務の実行とメッセージをメッセージテーブルに入れる操作を同じトランザクション内に配置します。これにより、メッセージがローカル テーブルに格納されたときにビジネスが正常に実行されることが保証されます。 次に次の操作を呼び出します。次の操作呼び出しが成功した場合、メッセージ テーブルのメッセージ ステータスを直接成功に変更できます。 通話が失敗しても大丈夫です。バックグラウンド タスクは、定期的にローカル メッセージ テーブルを読み取り、成功しなかったメッセージをフィルター処理して、対応するサービスを呼び出します。サービス更新が成功すると、メッセージのステータスが変更されます。 このとき、メッセージに対応する操作が成功しない可能性があるため、再試行も必要です。再試行するには、対応するサービス メソッドがべき等であることを確認する必要があります。さらに、通常、再試行回数には上限があります。最大数を超えた場合は、手動で処理するためにアラームを記録することができます。 ローカル メッセージ テーブルは実際に最終的な一貫性を実現し、一時的なデータの不整合を許容していることがわかります。 メッセージ トランザクション RocketMQ はメッセージ トランザクションを非常に適切にサポートします。メッセージを通じてトランザクションを実装する方法を見てみましょう。 最初のステップは、トランザクション メッセージ、または半分のメッセージをブローカーに送信することです。半分のメッセージとは、メッセージが半分であるという意味ではなく、メッセージが消費者に見えないという意味です。メッセージが正常に送信された後、送信者はローカル トランザクションを実行します。 次に、ローカル トランザクションの結果に基づいて、ブローカーに Commit または RollBack コマンドを送信します。 さらに、RocketMQ の送信者は、逆クエリトランザクションステータスインターフェイスを提供します。セミメッセージが一定期間内に操作要求を受信しない場合、ブローカーはリバース クエリ インターフェイスを使用して送信者のトランザクションが正常に実行されたかどうかを確認し、Commit コマンドまたは RollBack コマンドを実行します。 コミットの場合、サブスクライバーはメッセージを受信してから、対応する操作を実行します。完了後、サブスクライバーはメッセージを消費できます。 RollBack の場合、サブスクライバーはメッセージを受信せず、トランザクションが実行されていないことを意味します。 RocketMQ を使用すると比較的簡単に実装できることがわかります。 RocketMQ はトランザクション メッセージの機能を提供します。トランザクション逆クエリ インターフェイスを定義するだけで済みます。 メッセージ トランザクションでも最終的な一貫性が実現されていることがわかります。 ベストエフォート通知 実際、ローカル メッセージ テーブルもベスト エフォートと見なすことができ、トランザクション メッセージもベスト エフォートと見なすことができると思います。 ローカル メッセージ テーブルに関しては、未完了のメッセージを定期的にチェックし、対応するサービスを呼び出すバックグラウンド タスクが実行されます。メッセージが複数回呼び出されなかった場合は、記録して手動で導入するか、直接破棄することができます。これは実際、最善の努力です。 トランザクション メッセージについても同様です。セミメッセージがコミットされると、通常のメッセージになります。サブスクライバーがそれを消費しない、または消費できない場合は、再試行が続けられ、最終的にデッドレター キューに入ります。実際、これも最善の努力です。 したがって、ベスト エフォート通知は、実際には柔軟なトランザクションのアイデアを表現しているだけです。つまり、トランザクションの最終的な一貫性を実現するために最善を尽くしました。 SMS 通知など、時間に敏感でないサービスに適用されます。 要約する 2PC と 3PC は一種の強力な一貫性トランザクションであることがわかりますが、データの不整合やブロッキングなどのリスクがまだ存在し、データベース レベルでのみ使用できます。 TCC は、より広範囲に適用できる補償取引の概念です。これはビジネス レベルで実装されるため、ビジネスへの影響がより大きくなります。各操作には、対応する 3 つのメソッドの実装が必要です。 ローカル メッセージ、トランザクション メッセージ、およびベスト エフォート通知は、実際には最終的に一貫性のあるトランザクションであるため、時間に敏感でない一部のビジネスに適しています。 |
<<: Kubernetes、エッジからコア、クラウドまでをカバーするコンテナ
>>: Kubernetes は Meituan のクラウド インフラストラクチャをどのように変えるのでしょうか?
楊俊俊:Black People Networkは、多くの業界の専門家やメディアにとって必修科目とな...
[[402974]]この記事はWeChatの公開アカウント「プログラマーjinjunzhu」から転載...
friendhosting VPSには9つのデータセンターがありますが、国内での使用に適しているのは...
Prometeus は最近、多くの割引を提供しています。同社は 1 か月以内に、大容量メモリと大容量...
rcp.net は、今年のブラックフライデー特別プロモーションを開始しました。日本 VPS は、Tr...
2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますランニング...
金庸師の六経神剣は、指先の内力を利用して遠くから敵を倒す武術で、繊細で軽快、そして素早い技です。新し...
ある日、偶然 SEO 愛好家を訪ねて、次のような記事を目にしました。「PR の低下は、ウェブサイトの...
これまで、企業のマーケティング戦略は、従来の広告や実店舗を通じて消費者との関係を構築することでした。...
ご存知のとおり、電子商取引の発展に伴い、ウェブサイトの最適化における企業間の競争はますます激しくなっ...
2019 年に最も速い日本の VPS は何ですか?最も高速な日本の VPS/日本の高速 VPS はど...
私は現在、RCNTEC株式会社に勤務しており、日々分散環境に取り組んでいます。 ISC BIND を...
マイクロソフトとIDCがアジア太平洋地域で実施した調査「未来に備えたビジネス:AIによるアジアの成長...
中国インターネット発展に関する第29回統計報告によると、中国のウェブサイトの数は230万です。同時に...
数日前、グループでウェブサイトを構築している友人たちとチャットしていたとき、あるウェブマスターが、B...