web-dev-qa-db-ja.com

イベントソースシステムは、読み取りモデルが一度だけ更新されることをどのように確認しますか?

現在、マイクロサービスアーキテクチャのイベントソーシングを調べており、 この記事 を見つけました。 CQRSとイベントソースの可能な実装について説明します。
読み取りモデルの更新とイベントの作成を処理するロジックが両方とも同じ(スケーラブル)サービスに実装されている場合、このアーキテクチャでは、読み取りモデルを変更するイベントが1回だけ処理されることをどのように確認しますか?

記事で使用した例で明確にするために: https://cdn-images-1.medium.com/max/900/1*kyDtZ3pq6ht3Zp5C5ksTvQ.png User Management Serviceがスケーリングしていて、同時に複数のインスタンスが実行されている場合、ロジックが同じユーザーを2回追加しないようにするにはどうすればよいですか?

私が考えることができる1つの解決策は、すべてのマイクロサービスを1つの読み取りサービスと1つの書き込みサービスに完全に分割し、読み取りサービスのみをスケーリングすることですが、それは最適に聞こえません。

4
Torsten N.

User Management Serviceがスケーリングしていて、同時に複数のインスタンスが実行されている場合、ロジックが同じユーザーを2回追加しないようにするにはどうすればよいですか?

ここにいくつかの異なる答えがあります。

良い出発点は、Marc de Graauwの Nobody Needs Reliable Messaging です。基本的な考え方は、複数のメッセージのビジネスセマンティクスが同じであるため、コンシューマ自身が重複を拒否できるということです。

つまり、プロトコルを逆方向​​に押し進めることになります。 「同じ」ユーザーを作成しようとする、おそらく時間的に分離されたHTTPリクエストの2つのコピーがある場合、それらのリクエストを処理するUser Management Serviceの2つのインスタンスは、意味的に同等のイベントをストアに追加しようとすることになります。

そのプロパティを設定すると、特定のコンシューマはメッセージのセマンティクスを使用して重複を排除できます。

イベントストアの実装は 条件付き発行 に役立ちます。たとえば、 Event Store は、書き込みコマンドで expected version パラメータをサポートすることでこれを実現します。

競合するプロデューサー間の競争では、2人のライターが、予想される同じバージョンのストリームに書き込むために競合します。 1番目のプロデューサーは成功し、2番目のプロデューサーは失敗します。2番目のプロデューサーは、ローカルにキャッシュされたストリームの表現が古くなっていることを認識しています。その後、キャッシュを更新して、メッセージの処理を再試行できます。

つまり、イベントコレクションへの書き込みは、「append」ではなく、末尾参照の compare and swap によって行われます。

私の知る限りでは、2017年のKafkaは条件付き公開をサポートしていません。 .11exactly once delivery 機能はサポートしていませんそのケースを処理するように見えます。

同じイベントパーティションに書き込む複数のプロセスは、希望どおりにならない場合があります。単一の機関の振る舞いを推論することはとても簡単です。 User Management Serviceの複数のインスタンスが単一のストリームへの書き込み権限を共有する代わりに、それぞれが単一の権限を持つ複数のストリームを作成する方がよい場合があります(基本的に、各個別のストリームには独自の リーダー選挙があります。 )。

しかし、最後の部分を100%理解したとは思いません。次のようになりますか:i.imgur.com/b0C2xNV.png?

はい、各ユーザーサービスには、出力イベント用の独自のトピック(記録簿)があるという意味で。ただし、モデル内の特定のエンティティに関連するすべてのイベントが同じトピックに確実に書き込まれるようにする必要もあります。そのため、各コマンドがそのエンティティのユーザーサービスの権限のあるインスタンスによって確実に処理されるようにする責任のあるロジックがあります。

6
VoiceOfUnreason

Apache Kafkaには、コンシューマーパーティションのサブスクリプションを自動的に管理する機能が含まれているため、特定のコンシューマーグループ(ユーザー読み取りモデルアップデーターコンシューマーグループなど)の単一のコンシューマーのみが特定のパーティションにサブスクライブされます-コンシューマー間でパーティションのバランスを自動的に調整します(バックグラウンドでZooKeeperを使用してこれを管理します)。したがって、ユーザー管理サービスをスケーリングすると、新しいインスタンスが自身を読み取りモデルのアップデーターコンシューマーグループに追加し、パーティションのバランスが再調整されます異なるコンシューマ間で。インスタンスを削除すると、そのパーティションはグループ内の残りのコンシューマに追加されます。これにより、特定の読み取りレコードに一度に1つのプロセスとスレッドのみが書き込み、同時実行性の問題を防ぐことができます。

Zookeeperでの書き込み側はトリッキーです。克服すべき2つの問題があります。

  1. Zookeeperは、アグリゲートごとに別個のストリームを持つことを処理できません(主に、ストリームごとに処理されるパーティション管理のため、過去数千のストリームをスケーリングしません)。これは、特定のタイプ(ユーザーなど)のすべての集合体が単一のストリームを共有する必要があることを意味します。
  2. Zookeeperには(楽観的ロックまたは悲観的ロックのいずれかを使用して)書き込み用にストリームをロックする組み込みの手段がないため、特定のアグリゲートのイベントが自分でシリアルにコミットされるようにする必要があります。

最初の問題は、そのイベントをロードすることで単一の集約を実際にロードすることができないことを意味します(たとえばall Usersのイベントしかロードできないため、これは数百万のイベントになる可能性があります)。代わりに、すべてのアグリゲートの一貫したスナップショット(メモリ内またはストア内、規模と許容される起動待ち時間に応じて)。イベントを公開するたびに、すぐにスナップショットを保存します(そして、スナップショットが機能するか、プロセスが終了するまで再試行します)。起動時に以前の障害が発生した場合、最後に書き込まれたスナップショットのイベント番号を算出し、そこからイベントを再処理して、不足しているスナップショットを埋める必要があります。もちろん、スナップショットをメモリにキャッシュできます。

2番目の問題を処理するための1つのオプションは、1つのスレッド+プロセスのみが特定の集約を処理するようにすることです。あなたが言うように、最も簡単な方法はただ一人の作家を持つことです。または、(イベントではなく)コマンドを別のKafkaトピックに投稿し、パーティション管理を再度使用してパーティションをコマンドハンドラーに割り当てることもできます。特定の集約のすべてのコマンドがコマンドハンドラーは、コマンドメタデータで指定されたトピックに返信を投稿する可能性があるため、APIゲートウェイが使用可能なときにコマンド結果を返すことができます。

1
TomW