私はマイクロサービスアーキテクチャに比較的慣れていません。適度なサイズのWebアプリケーションがあり、現在進めているモノリシックシステムではなく、マイクロサービスに分割することの長所と短所を比較検討しています。
私が理解している限り、マイクロサービスA
とB
を検討してください。それぞれのマイクロサービスは、他方が持っているデータのサブセットに依存しています。 A
によって何かが変更されたというメッセージが投稿された場合、B
はそのメッセージを消費し、A
の情報のローカルコピーを複製し、それを使用して何でもできますB
を実行する必要があります。
ただし、B
がダウン/失敗し、しばらくすると、再びアップした場合はどうなりますか。そのダウンタイム中に、A
はさらに2つのメッセージを公開しました。 B
は、A
の情報のローカルコピーを更新する方法をどのように知っていますか?
確かに、B
がA
のキューの唯一のコンシューマーである場合、オンラインに戻ったときに読み取りを開始できますが、そのキューの他のコンシューマーがあり、それらのメッセージが消費されるとどうなりますか?
より具体的な例として、Users
マイクロサービスがダウンしているときにBilling
サービスの電子メールアドレスが更新された場合、Billing
マイクロサービスが再びアップした場合、それはどのようにしてわかりますかメールが更新されたことを
マイクロサービスが復旧すると、「おい、復旧しました。現在の情報をすべて教えてください」というブロードキャストが行われますか?
一般に、データ同期の業界でのベストプラクティスは何ですか?
もう少し調査した後、私は偶然に this の記事に出くわしました。この記事から、自分が達成したいこと(および将来の読者にとって)に役立つと思う引用をいくつか引き出しました。これは、命令型プログラミングモデルよりもリアクティブプログラミングモデルを採用する方法を提供します。
イベントソーシング
ここでの考え方は、すべてのアプリケーションの状態遷移を不変のイベントの形で表すことです。その後、イベントは発生時にログまたはジャーナル形式で保存されます(「イベントストア」とも呼ばれます)。また、アプリケーション全体の状態が時間の経過とともにどのように変化したかを表すために、無期限にクエリを実行して保存することもできます。
これが達成に役立つのは、マイクロサービスがダウンした場合、それに関連する他のイベントが公開され、イベントは、そのマイクロサービスが来たときに、たとえばそのマイクロサービスの他のインスタンスによって消費されるということです。バックアップすると、このevent store
を参照して、ダウンした期間中に失われたすべてのイベントを取得できます。
Apache Kafka as Event Broker
Apache Kafkaの使用を検討してください。これは、毎秒数千のイベントを格納およびディスパッチでき、組み込みのレプリケーションおよびフォールトトレランスメカニズムを備えています。ディスクに格納できる永続的なイベントストアがあります。無期限に、いつでも(ただし削除されずに)消費され、トピック(Kafkaのファンシーキュー)から配信されました。
次に、イベントにオフセットが割り当てられ、トピック内でそれらを一義的に識別します— Kafkaはオフセット自体を管理でき、「最大1回」または「少なくとも1回」の配信セマンティクスを簡単に提供できますが、イベントコンシューマーがトピックに参加したときに交渉することもできます。 、マイクロサービスが時間内の任意の場所からイベントの消費を開始できるようにします。ユースケースが「正常に完了」したときに、最後に消費されたイベントオフセットがトランザクションのサービスのローカルストレージに永続化されている場合、そのオフセットを使用して「正確に1回」のイベント配信セマンティクスを簡単に実現できます。
実際、コンシューマーがKafkaに対して自分自身を識別した場合、Kafkaは、どのメッセージがどのコンシューマーに配信されたかを記録し、再度提供されないようにします。
佐賀
異なるサービス間の通信が本当に必要な、より複雑なユースケースの場合、ユースケースを完了する責任は十分に認識されなければなりません—ユースケースは分散化され、関係するすべてのサービスがタスクの正常完了を認めたときにのみ終了します。それ以外の場合は、ユースケース全体が失敗する必要があります。無効なローカル状態をロールバックするには、修正措置をトリガーする必要があります。
これは、佐賀が登場するときです。サガは一連のローカルトランザクションです。各ローカルトランザクションはデータベースを更新し、メッセージまたはイベントを発行して、サガの次のローカルトランザクションをトリガーします。ビジネスルールに違反しているためにローカルトランザクションが失敗した場合、Sagaは、先行するローカルトランザクションによって行われた変更を元に戻す一連の補正トランザクションを実行します。詳細は this を参照してください。
「他のすべてのマイクロサービスにデータをプッシュする」というあなたの考え全体に挑戦します。
通常、課金サービスがメールアドレスを必要とする場合、特定の顧客のメールアドレスをアドレスサービスに要求します。すべての住所データのコピーを保持する必要はなく、何か変更があった場合は通知されません。最新のデータから質問して答えを得るだけです。
たとえ遅くても、2セントで議論することにします。イベント駆動型マイクロサービスアーキテクチャの設計を評価するときに重要なポイントだと思うからです。各マイクロサービスは、その状態に影響を与えるイベントを正確に認識しており、それらを待つことができます。マイクロサービスが利用できない場合は、失敗したマイクロサービスから必要なメッセージを、それを「消費」することができないまで保持するコンポーネントが必要です。これは実際には「プロデューサー/コンシューマー」モデルであり、「パブリッシュ/サブスクライブ」モデルではありません。メッセージブローカー(Kafka、RabbitMQ、ActiveMQなど)は通常、この動作を実現するための最良の方法です(イベントソースなどの別のものを実装していない場合を除く)。永続的なキューとack/nackメカニズムを提供します。
これで、マイクロサービスはメッセージが最終的に配信されることを認識しますが、それだけでは不十分です。これは、単一のメッセージの配信を期待する方法ですか?同じイベント通知の複数のコピーの配信を管理できますか?これは配信の意味の問題です(少なくとも1回、正確に1回)。
最終的な考え):
他の人からのイベントを消費する必要があるマイクロサービスをアーキテクチャに追加するときは、最初の同期を行う必要があります
ブローカーでさえ失敗する可能性があり、この場合、メッセージは失われます
どちらのシナリオでも、マイクロサービスの状態を元に戻す簡単なメカニズムがあると便利です。 A REST APIまたはメッセージを送信するスクリプトですが、最も重要なことは、メンテナンスタスクを実行する手段を持つことです。
通常のイベントキューをパブリッシャー/サブスクライバーモデルに置き換えることができます。ここで、A
サービスはトピックの新しいメッセージを発行します[〜#〜] t [〜#〜]およびB
タイプのマイクロサービスは同じトピックにサブスクライブします。
理想的には、B
はステートレスサービスであり、分離された永続化サービスを利用して、失敗したB
サービスインスタンスが1つ以上のB
サービスインスタンスを生成することによって置き換えられる同じ共有永続化サービスから読み取り、作業を続行します。
何かが変更されたというメッセージがAによって投稿された場合、Bはそのメッセージを消費し、Aの情報のローカルコピーを複製し、それを使用してBが実行する必要があることをすべて実行できます。
BがAの内部データにアクセスできるようにしたい場合は、Aの内部データベースへのアクセスを許可するほうがよいでしょう。
ただし、これを行うべきではありません。サービス指向アーキテクチャの要点は、サービスBがサービスAの内部状態を認識できず、REST API(およびその逆)を介したリクエストの作成に限定されていることです。
あなたのケースでは、すべてのユーザーデータを保存する責任があるユーザーデータサービスを持つことができます。そのデータを使用したい他のサービスは、必要なときにのみデータを要求し、ローカルコピーを保持しません(GDPRコンプライアンスについて考える場合、これは非常に便利です)。 User Dataサービスは、「新しいユーザーの作成」や「user_id 23の名前の変更」などの単純なCRUD操作をサポートできます。または、「次の2週間で誕生日が来るすべての標準ユーザーを見つけて提供する」など、より複雑な操作を行うことができます。プレミアム試用ステータス」。課金サービスがユーザー42にメールを送信する必要がある場合、ユーザーデータサービスに「user_id 42のメールアドレスは何ですか」と尋ね、すべての課金情報とともに内部データを使用してメールを作成し、メールサーバーへのメールアドレスと本文。