これは古典的な問題であり、多くの異なる人々によって何度も解決されているはずです。私は正式なトレーニングを受けていないため(コンピュータサイエンスやその他の学問的なテーマを勉強していません)、これから説明しようとしている問題を解決する最良の方法がわかりません。
以下の図が銀行家のジレンマの例であると想像すると(2人のユーザーFooとBarが単一の銀行口座にアクセスできます:Baz)。示されているパスの1つをたどるときに予想される動作は何ですか
注:Baz変数でミューテックス(または他の同期形式)を使用していると想定しています。
例1:Bazは最初に値10を保持します。Fooが新しい値(現在の値から5を削除した結果)をBarの前に書き込んだ場合。その後、バーは新しい値5から10を取り、マイナスのバランスを残します(つまり、最終的な値は-5になります)。利用可能なよりも多くのお金がかかっていることを意味します。
例2:Bazは最初に値10を保持しています。BarがFooの前に新しい値(現在の値から10を削除した結果)を書き込んだ場合。その後、Fooは新しい値0から5を取り、マイナスのバランスを残します(つまり、最終的な値は-5になります)。利用可能なよりも多くのお金がかかっていることを意味します。
両方のアクション(Foo (-5)
およびBar (-10)
)は同時にトリガーされます。では、FooまたはBarのどちらかが、トランザクションを完了できない(アラートを成功させるための十分な資金がないため)ことを通知することをどのように保証しますか?
潜在的な解決策は、呼び出し側が内部でミューテックスを使用するメソッドを実行して値を最初にロックすることを保証することです。その後、値がロックされると、値を読み取ることができます。次に、アクションが有効かどうかを確認します。条件が満たされた場合、値を更新し、値のロックを解放します。次の呼び出し元が値をロックして、同じ手順を実行できることを意味します。
しかし、このアプローチは分散システムでどのように機能しますか?グローバルデータストアの使用を提案することもできますが、整合性を保証するものでなければなりません(たとえば、AWSのDynamo DBなどのサービスは「結果整合性」を提供するため、銀行機関では機能しません)。しかし、保証された一貫性は一般に非常に遅いと考えられています(私が想定する分散ノードの数によって異なります)。
では、この設計問題をどのように解決しようとするのでしょうか。
分散システムの場合、次のいずれかを行います。
a)「金額を減算するか、できない場合はエラーを返す」を使用します。ここで、baz
を担当するコードは、結果が負の場合はエラーを返します(または、結果がなかった場合は「成功」を返します)エラー)
b)ロックと同等のものを使用します。ここで、baz
を担当するコードには、「取得バズ」と「リリースバズ」があり、その前後に使用する必要があります。
これは通常、氷山の一角にすぎないことに注意してください。 2つ以上の銀行口座があり、1つの口座から他の口座に資金を振り替えて、すべての口座が更新されるか、まったく更新されない可能性が高くなります。この場合、(たとえば)組み合わせになる可能性があります。
たとえば、「Fred」と「Jane」という2つのアカウントがあり、5ドルをFredからJaneに送金したい場合、その後、次のようなシーケンスになる可能性があります。
あなたからフレッドのアカウントへ:「フレッドのアカウントが5以上の場合、フレッドのアカウントをロックして、続行できることを教えてください。それ以外の場合は、続行できないことを教えてください」
フレッドのアカウントからあなたへ:「続行できます」
あなたからジェーンのアカウントへ:「ジェーンのアカウントを5増やすことができる場合、ジェーンのアカウントをロックして、続行できることを伝え、それ以外の場合は続行できないことを伝えます」
ジェーンのアカウントからあなたへ:「続行できます」
あなたからフレッドのアカウントへ:「フレッドのアカウントから5を引いて、以前にくれたロックを解除して」
あなたからジェーンのアカウントへ:「ジェーンのアカウントに5を追加し、以前にくれたロックを解除して」
この例では、あなた、Fredのアカウント、Janeのアカウントはすべて、メッセージやパケットと通信する完全に異なるコンピュータで実行されている可能性があります(共有メモリはまったくありません)。
私は、金融業界が「事後」のチェックと修正のシステムを使用してエラーを解決することを理解しています。
つまり、個々のシステムで各トランザクションを独立して(各システムが正しいことがわかっているように)、各トランザクションの詳細をログに書き込みます。これらのログは後で比較され、一方でエラーが発生した場合、もう一方はそのトランザクションをロールバックするように指示されます。
したがって、銀行Aはお金を引き出すことに成功しましたが、銀行Bはそれを入金できませんでした。後で、両方のトランザクションリストが比較され、銀行Aは物事を正しくするためのクレジットを取得します。
このようなシステムでは分散「ロック」を実装することはできません。期待される方法で応答しないためです。また、他のシステムが関与するまでの期間がわからない場合、引き出し中に誰かのアカウントをロックしたくないトランザクションが完了するまでにかかると、そのアカウントの他のトランザクションをブロックしているロックが開いたままになる可能性があります。