web-dev-qa-db-ja.com

データベースとKafkaプロデューサー間のトランザクションの同期

Kafka=はサービス間の通信メカニズムとして使用されます。一部のサービスには独自のデータベースがあります。ユーザーがサービスAを呼び出すと、さらに、このイベントは、Kafkaトピックの項目として、他のサービスに報告される必要があります。最良の方法は何ですか? Kafkaトピックが正常に更新された場合にのみデータベースレコードが書き込まれるようにします(基本的に、データベース更新およびKafka= )?

spring-kafka (Spring Boot WebFluxサービスで)を使用することを考えていますが、 KafkaTransactionManager があることがわかりますしかし、私が理解していることから、これは2つのシステム間でトランザクションを同期するのではなく、Kafkaトランザクション自体(Kafkaプロデューサーとコンシューマー間で一貫性を確保する)についてです。 here を参照してください:「KafkaはXAをサポートしていないため、Kafka txロール中にDB txがコミットする可能性に対処する必要がありますさらに、このクラスはSpringのトランザクションフレームワークに依存していると思います。少なくとも現在理解している限りでは、スレッドにバインドされており、リアクティブアプローチ(WebFluxなど)の異なる部分を使用すると動作しません操作は異なるスレッドで実行できます( reactive-pg-client を使用しているため、Springのフレームワークを使用するのではなく、手動でトランザクションを処理しています)。

私が考えることができるいくつかのオプション:

  1. データベースにデータを書き込まないでください。Kafkaにのみ書き込みます。次に、サービスAのコンシューマを使用してデータベースを更新します。これは、最も効率的ではないようで、ユーザーが呼び出したサービスは、作成したデータベースの変更をすぐに見ることができないという問題があります。
  2. Kafkaに直接書き込まないでください。データベースにのみ書き込み、 Debezium などを使用して、変更をKafkaに報告します。ここでの問題は、変更が個々のデータベースレコードに基づいているのに対し、Kafkaに保存するビジネス上の重要なイベントには複数のテーブルのデータの組み合わせが含まれる場合があることです。
  3. 最初にデータベースに書き込みます(それが失敗した場合、何もせずに例外をスローします)。次に、Kafkaに書き込むときに、書き込みが失敗する可能性があると想定します。組み込みの自動再試行機能を使用して、しばらく試してみてください。最終的にそれが完全に失敗する場合は、デッドレターキューに書き込み、管理者が整理するための何らかの手動メカニズムを作成してみてください。そして、DLQへの書き込みが失敗した場合(つまりKafkaが完全にダウンした場合)、それを別の方法(データベースなど)でログに記録し、管理者がソートするための何らかの手動メカニズムを再度作成しますでる。

上記のことについて誰もが考えやアドバイスを受けましたか、上記の私の仮定の間違いを修正できますか?

前もって感謝します!

12
Yoni Gibbs

アプローチ2のわずかに変更したバリアントを使用することをお勧めします。

データベースにのみ書き込みますが、実際のテーブル書き込みに加えて、同じデータベース内の特別なテーブルにも「イベント」を書き込みます。これらのイベントレコードには、必要な集計が含まれます。最も簡単な方法では、別のエンティティを挿入するだけです。 JPAによってマッピングされます。これには、集約ペイロードを持つJSONプロパティが含まれます。もちろん、これはトランザクションリスナ/フレームワークコンポーネントの何らかの手段によって自動化できます。

次に、Debeziumを使用して、そのテーブルから変更をキャプチャし、Kafkaにストリームします。そうすれば、両方があります:Kafka(最終的にKafkaのイベントが遅れるか、再起動後にいくつかのイベントが表示される場合があります、しかし、最終的には、分散トランザクションやビジネスレベルのイベントセマンティクスを必要とせずに、データベースの状態を反映します)。

(免責事項:私はDebeziumのリーダーです。おもしろいことに、このアプローチを詳細に説明するブログ投稿を書いている最中です)

投稿はこちら

https://debezium.io/blog/2018/09/20/materializing-aggregate-views-with-hibernate-and-debezium/

https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/

11
Gunnar

まず第一に、私はKafkaでもSpringの専門家でもないということを言わなければなりませんが、独立したリソースに書き込むときは概念上の課題であり、ソリューションはテクノロジースタックに適応できるはずです。さらに、このソリューションはDebeziumのような外部コンポーネントなしで問題を解決しようとしていると言う必要があります。私の意見では、コンポーネントを追加するたびに、そのようなオプションを選択するときに過小評価されるアプリケーションのテスト、保守、実行に課題が生じるためです。また、すべてのデータベースをDebeziumソースとして使用できるわけではありません。

同じ目標について話していることを確認するために、顧客がチケットを購入できる単純化された航空会社の例で状況を明確にしましょう。注文が正常に完了すると、顧客は外部のメッセージングシステム(通話する必要のあるシステム)から送信されるメッセージ(メール、プッシュ通知など)を受け取ります。

データベース(注文を保存する)とJMSプロバイダーの間にXAトランザクションがある従来のJMSの世界では、次のようになります。クライアントは、トランザクションを開始するアプリに注文を設定します。アプリは注文をデータベースに保存します。次に、メッセージがJMSに送信され、トランザクションをコミットできます。両方のオペレーションは、自分のリソースと話している場合でもトランザクションに参加します。 XAトランザクションでACIDが保証されるため、問題ありません。

ゲームにKafka(またはXAトランザクションに参加できない他のリソース)を持ち込みましょう。両方のトランザクションを同期するコーディネーターはもうないので、以下の主なアイデアは永続的な状態の2つの部分に分割処理。

データベースに注文を保存するとき、Kafka=に送信したいのと同じデータベース(CLOB列のJSONとして)にメッセージ(集約データ)を保存することもできます。 。同じリソース– ACIDが保証され、これまでのところすべて問題ありません。Kafka-Topicに送信する新しいタスクの「KafkaTasks」テーブルをポーリングするメカニズムが必要です(たとえば、タイマーサービス、@ Scheduledアノテーションを使用できます)メッセージがKafkaに正常に送信された後、タスクエントリを削除できます。これにより、Kafkaへのメッセージは、注文もアプリケーションデータベースに正常に保存されます。XAトランザクションを使用する場合と同じ保証を達成できましたか?残念ながら、まだKafkaが動作する可能性がありますが、タスクの削除は失敗します。この場合、再試行メカニズム(質問で述べたように必要です)がタスクを再処理し、混乱を送信します。二歳。ビジネスケースがこの「少なくとも1回」の保証に満足している場合は、フレームワーク機能として簡単に実装できるように、誰もが詳細に煩わされる必要はありません。

「1回だけ」が必要な場合、アプリケーションデータベースに状態を保存することはできません(この場合、「タスクの削除」が「状態」です)代わりにKafka =(2つのKafkaトピック)間のACID保証があると仮定します。例:テーブルに100個のタスク(ID 1〜100)があり、タスクジョブが最初の10個を処理するとします。 Kafka=メッセージをトピックに、IDが10の別のメッセージを「あなたのトピック」に書き込みます。すべて同じKafka-transactionで。次のサイクルでトピックを消費します(値は10 )そしてこの値を取得して、次の10個のタスクを取得します(そして、すでに処理されたタスクを削除します)。

同じ保証付きの簡単な(アプリケーション内の)ソリューションがある場合は、ご連絡をお待ちしています!

長い回答で申し訳ありませんが、お役に立てば幸いです。

2
Jonas