ユーザーのパスワードを更新する関数があるとします。
「パスワードの更新」ボタンをクリックすると、UpdatePasswordEventがトピックに送信され、他の3つのサービスがサブスクライブされます。
結果整合性について私が理解したことに基づいて、これらすべてのサービス(コンシューマー)は同時にイベントを受け取り、それらを個別に処理するため、適切なシナリオでは、データの整合性が保たれます。
ただし、サービスがイベントの処理に失敗した場合はどうなりますか?例えば突然の切断、データベースエラーなど...これらのトランザクションの失敗を処理するための適切なパターン/プラクティスは何ですか?
イベントの処理に失敗した場合に、「ロールバックサービス」がその仕事を行い、データを元に戻すトピックにRollbackEventが作成されるRollbackTopicを作成することを考えていました。
結果の一貫性について私が理解したことに基づいて、これらのすべてのサービス(消費者)が同時にイベントを受信し、個別に処理します。適切なシナリオでは、データの一貫性が保たれます。
いいえ、必ずしもそうとは限りません。私がコメントしたように、送信されたメールを元に戻すことはできないため、一種の「シーケンス」が必要です。 [〜#〜] ipc [〜#〜]over event-driven data management はオーケステーションの対象外ではありません 1。
たとえば、前のトランザクションが正常に完了し、電子メールサービスがその証拠を取得しない限り、電子メールは送信されません。 3
ただし、サービスがイベントの処理に失敗した場合はどうなりますか?例えば突然の切断、データベースエラーなど...これらのトランザクションの失敗を処理するための適切なパターン/実践は何ですか?
分散コンピューティングの誤解 にこんにちは。それらは物事を複雑にし、いつものように、それらに対処するための銀の弾丸はありません。
ロストアークを探す旅を始める前に、まず組織に質問することを検討する必要があります。多くの場合、解決策は組織が現実の世界でこれらの問題にどのように直面しているかです。
特定のデータが欠落している、または不完全な場合、全員(部門)は何をしますか?
部門ごとに異なるソリューションがあり、それらが全体として、実装されるソリューションを構成していることに気付くでしょう。
とにかく、ここでは、従うべき戦略で私たちを助けることができるいくつかの実践があります。
システムが常に一貫した状態であることを保証するのではなく、代わりに、システムが将来のある時点でそれを取得することを受け入れることができます。このアプローチは、長期にわたるビジネスオペレーションに特に役立ちます。
システムが一貫性に到達する方法は、システムによって異なります。自動化されたプロセスから、ある種の人間の介入までを含む場合があります。たとえば、典型的な後でもう一度試すまたはカスタマーサービスに連絡してください。
補償トランザクションを使用して、システムを一貫した状態に戻します。ただし、これらのトランザクションも失敗する可能性があることを考慮する必要があります。これにより、不整合を解決することがさらに困難になる場合があります。また、送信したメールを元に戻すことはできません。
トランザクション数が少ない場合、補償トランザクションの数も少ないため、このアプローチは実現可能です。 IPCに関係するビジネストランザクションが複数ある場合、それぞれに対して1つの補償トランザクションを処理することは困難です。
補償トランザクションを使用すると、 サーキットブレーカーの設計パターン が非常に役立つことがわかります-と必須あえて言う-
アイデアは、Transaction Managerと呼ばれる全体的な管理プロセスを通じて、単一のトランザクション内の複数のトランザクションにまたがることです。分散トランザクションを処理するための一般的なアルゴリズムは Two-phase commit です。
分散トランザクションの主な関心事は、存続期間中のリソースのロックに依存していることであり、ご存じのように、Transaction Managerでも問題が発生する可能性があります。
Transaction Managersが危険にさらされると、さまざまな境界コンテキスト全体でいくつかのロックが発生し、メッセージのエンキューが原因で予期しない動作が発生する可能性があります。 2
既存のシステムを分解していて、本当に単一のトランザクション境界内に収めたいと考えている概念のコレクションを見つけた場合は、最後まで残してください。
サム・ニューマン
上記の引数に沿って、Sam-彼の本Building Microservices-には、本当に、本当に結果の一貫性を提供できない場合は、操作を分割することを避けるべきであると述べています今。
特定の操作を2つ以上のトランザクションに分割する余裕がない場合、おそらくこれらのトランザクションは同じ境界付きコンテキストに属しているか、少なくともモデル化されていない分野横断的なコンテキストに属していると言えます。
たとえば、私たちの場合、トランザクション#1と#2は互いに密接に関連しており、おそらく両方が同じ境界コンテキストAccounts、Users、Register、何でも...
同じトランザクションの境界内に両方の操作を配置することを検討してください。それは全体の操作を扱いやすくします。また、各トランザクションの重要度のレベルも示します。おそらく、トランザクション#2が失敗しても、操作全体が損なわれることはありません。疑問がある場合は、組織に問い合わせてください。
1:考えているようなオーケストレーションではありません。 ESBのオーケストレーションについて話しているのではありません。サービスを適切なイベントに反応させることについて話している。
2:分散トランザクションに関して興味深い Sam Newmanの意見 を見つけるかもしれません。
3:このテーマに関するDavid Parkerの回答を確認してください。
あなたの場合、3つすべてを一度に処理することはできません。必要なのはプロセスです。これは非常に単純化された例です:
状態変更操作は常に整合性のあるエンティティで行わなければならないことを知っておくことが重要です。 強い整合性 を保証できない場合は、マスターレコードで作成する必要があります。
システムでは、システムでイベントが発生する前に、トランザクションの安全性を維持して変更を永続化する必要があることをシステムは保証する必要があります。これは、発生したイベントが本当に実際に起こったことの確認であることを保証するためです。
プロセスにはいくつかの注意が必要な部分があり、次のような明白な部分は無視します。たとえば、パスワードを変更したユーザーを永続化するときにデータベースサーバーが停止した場合はどうなりますか?もう一度UpdatePasswordを発行するだけです。ただし、一部の部品はお客様が処理する必要があります。これらは次のとおりです。
システムでは、プロセスオーケストレーター(PO)は他のエンティティに他なりません。これには、文字どおりの内部状態も含まれ、状態間の遷移を可能にし、ある種の状態マシンとして効果的に機能します。内部状態のおかげで、メッセージの重複処理を削除できます。
POがNew
状態にあり、UserPasswordHasBeenUpdated
を処理する場合、POはその状態をUserPasswordHasBeenUpdated
に変更します(またはどの状態名でも機能します)。 POがまだUserPasswordHasBeenUpdated
にあり、別のUserPasswordHasBeenUpdated
が到着する場合、POはメッセージが完全に無視され、重複であることを認識します。他の州にも同様のメカニズムが実装されます。
電子メールの実際の送信の処理は少しトリッキーです。ここには2つのオプションがあります。
このオプションを使用すると、POがUserPasswordHistoryHasBeenSaved
状態に達すると、状態の変化に対する反応として、電子メールを送信するコマンドがディスパッチされます。システムは、電子メールを送信する前にUserPasswordHistoryHasBeenSaved
状態が保持されることを確認します。つまり、重複したメッセージによって電子メールの送信が再度トリガーされることはありません。このアプローチでは、POの正しい状態が確実に保存されますが、その後の操作は保証できません。
これは私が行くものです。
UserPasswordHistoryHasBeenSaved
を保存して、それに対する反応として電子メールを送信する代わりに、最初に電子メールを送信しようとします。送信操作が失敗した場合、POの状態はUserPasswordHistoryHasBeenSaved
に変更されず、同じタイプの別のメッセージが引き続き処理されます。電子メールの送信は実際に成功しても、システムがPOの永続化中に新しいUserPasswordHistoryHasBeenSaved
状態で失敗する場合、UserPasswordHistoryHasBeenSaved
の別のメッセージがコマンドをトリガーして、電子メールとユーザーはそれを複数回受信することになります。
あなたのケースでは、ユーザーが実際に電子メールを受信することを確認したいとします。そのため、最初のオプションよりも2番目のオプションを選択します。
キューイングシステムは、あなたが考えるほど脆弱ではありません。
3つのプロセスすべてをリレーショナルデータベースに書き込んだ場合、トランザクションを使用して中間プロセスの障害を処理できます。
最終的なコミットがなければ、部分的な作業は破棄されます。
キューベースシステムでは、キューからメッセージを読み取って中間プロセス障害を処理するときに、同様のオプションがあります。
たとえば、Amazon SQSは、読み取られたメッセージを単に非表示にします。最後の削除コマンドが送信されない限り、メッセージは再表示されるか、デッドレターキューに入れられます。
同様の「トランザクション」をさまざまな方法で実装できます。基本的には、処理が成功したことの確認を受け取るまでメッセージのコピーを保持します。確認が間に合わない場合。メッセージを再度送信するか、手動で注意するために保持できます。
潜在的には、これらのエラーメッセージを監視し、関連するメッセージと過去の状態を把握してロールバックを実行する「ロールバックサービス」を作成できます。
しかしながら!通常は、エラーが発生したメッセージを再送信するだけです。結局、これらはエッジのケースになる傾向があります。サーバーに致命的な障害が発生したか、特定のメッセージタイプの処理にバグがありました。
エラーが通知されると、サービスを修復してメッセージを正常に処理できます。システム全体を一貫した状態に戻します。
ここで直面しているのは 2つの一般的な問題 です。本質的には、メッセージが受信され、そのメッセージへの応答が発生することをどのように確認できますか?多くの場合、完璧なソリューションは存在しません。実際、分散システムでは、多くの場合 正確に1回の配信を取得することは不可能 のメッセージです。
最初の明らかな発言は、パスワードを変更するサービスがパスワード変更イベントを送信する必要があるということです。このように、パスワード履歴とメール送信サービスは、パスワードが変更された理由に関係なく、パスワードが実際に変更されたときにのみトリガーされます。
実際に問題を解決するために、分散トランザクションを検討するのではなく、少なくとも1回のメッセージ配信とべき等処理の方向を調べます。
少なくとも一度は
パスワード変更イベントが実際にすべてのコンシューマーに表示されるようにするには、メッセージを「少なくとも1回」スタイルで消費できる耐久性のある通信チャネルを使用する必要があります。コンシューマーは、メッセージが完全に処理されたときに、メッセージが消費されたことを確認するだけです。たとえば、履歴エントリの書き込み中にパスワード履歴サービスがクラッシュした場合、再起動後に同じパスワード変更イベントを再度読み取り、再試行して、それ自体が履歴に書き込まれた後にそのイベントを読み取り専用として認識します。メッセージが確認されるまでメッセージを再送信する機能に基づいて、メッセージキューソリューションを選択する必要があります。
べき等
少なくとも1回の配信を実現した後、コンシューマが中断されて後で再処理される前にメッセージが部分的に処理されると、アクションが重複するという問題があります。これは、べき等になるように各サービスを設計することによって解決する必要があります。実行する書き込みは、悪影響を与えずに複数回発生する可能性があります。または、実行したアクションの独自のストアを保持し、アクションを複数回実行しないようにします。メール送信の場合、おそらくべき等の動作をさせようとする価値はなく、たまにメールが2回送信されるだけで問題ないことがわかるでしょう。
いずれにせよ、サービスをどの程度細かくするかに注意してください。あなたのパスワード履歴サービスは本当にパスワード変更サービスから独立している必要がありますか?
私は多くの答えに同意しません。
追加できる他の一貫性の約束があります。
これらの追加の整合性は、アプリケーションの動作に応じて実装する必要があります。
「履歴を更新する」とはどういう意味かわかりませんが、絶対に履歴を変更しないでください。 DAGを拡張するだけの場合は、これにより現在の状態が変化するはずです。それらは独立していません。もしそうなら、あなたは何が起こったかを反映している歴史に頼ることはできません。 (そして最後に重要なことですが、パスワードを保存しないでください パスワードを保存しない方法 を参照してください)