web-dev-qa-db-ja.com

Apache kafka consumerでメッセージの重複を避けるための効果的な戦略

私は今、Apache kafka=を1か月間勉強しています。しかし、今はある時点で立ち往生しています。私のユースケースは、異なるマシンで2つ以上のコンシューマープロセスを実行しています。 kafka server。で10,000メッセージを発行したいくつかのテスト。その後、これらのメッセージの処理中にコンシューマプロセスの1つを強制終了して再起動しました。消費者は処理済みメッセージをファイルに書き込みました。 、ファイルには10,000を超えるメッセージが表示されていたため、一部のメッセージが重複していました。

コンシューマープロセスで自動コミットを無効にしました。消費者は手動でオフセットをバッチ単位でコミットします。たとえば、100個のメッセージがファイルに書き込まれた場合、コンシューマはオフセットをコミットします。単一のコンシューマプロセスが実行されており、クラッシュして回復する場合、この方法で重複が回避されます。ただし、複数のコンシューマが実行されており、そのうちの1つがクラッシュして回復すると、重複したメッセージがファイルに書き込まれます。

これらの重複メッセージを回避するための効果的な戦略はありますか?

34
Shades88

短い答えは、いいえです。

探しているのは、1回だけの処理です。実行可能と思われることもありますが、常に注意すべき点があるため、これに頼るべきではありません。

重複を防ぐためにも、単純なコンシューマーを使用する必要があります。このアプローチがどのように機能するかは、各コンシューマーに対して、あるパーティションからメッセージがコンシュームされると、コンシュームされたメッセージのパーティションとオフセットをディスクに書き込みます。障害が発生した後、コンシューマが再起動したら、各パーティションの最後に消費されたオフセットをディスクから読み取ります。

しかし、このパターンを使用しても、コンシューマーは、障害後にメッセージを再処理しないことを保証できません。消費者がメッセージを消費し、オフセットがディスクにフラッシュされる前に失敗するとどうなりますか?メッセージを処理する前にディスクに書き込む場合、オフセットを書き込んでから実際にメッセージを処理する前に失敗するとどうなりますか?すべてのメッセージの後にZooKeeperにオフセットをコミットする場合でも、この同じ問題が存在します。

ただし、特定のユースケースに限って、1回限りの処理がより達成可能な場合があります。これには、ユニットアプリケーションの出力と同じ場所にオフセットを保存する必要があります。たとえば、メッセージをカウントするコンシューマーを作成する場合、各カウントで最後にカウントされたオフセットを保存することにより、オフセットがコンシューマーの状態と同時に保存されることを保証できます。もちろん、1回だけの処理を保証するには、メッセージを1つだけ消費し、各メッセージに対して状態を1回だけ更新する必要があります。これは、ほとんどのKafkaコンシューマアプリケーションでは完全に非実用的です。その性質Kafka=はパフォーマンス上の理由でバッチでメッセージを消費します。

通常、あなたの時間はよりよく費やされ、あなたのアプリケーションは、べき等であるように単純に設計すれば、はるかに信頼できるでしょう。

28
kuujo

これが Kafka FAQ が1回のみの件で言うべきことです:

Kafkaから1回だけのメッセージを取得するにはどうすればよいですか?

一度だけのセマンティクスには2つの部分があります。データ生成中の重複の回避とデータ消費中の重複の回避です。

データ生成中に1回だけセマンティクスを取得するには、2つのアプローチがあります。

  • パーティションごとにシングルライターを使用し、ネットワークエラーが発生するたびに、そのパーティションの最後のメッセージをチェックして、最後の書き込みが成功したかどうかを確認します
  • メッセージに主キー(UUIDまたは何か)を含め、コンシューマーで重複排除します。

これらのいずれかを実行すると、Kafkaホストはログに記録されません。ただし、重複なしの読み取りは、コンシューマーからの協力にも依存します。コンシューマーが定期的にチェックポイントを設定している場合その位置に障害が発生して再起動すると、チェックポイント位置から再起動するため、データ出力とチェックポイントがアトミックに書き込まれない場合、ここでも重複が発生する可能性があります。データベースを使用している場合、トランザクションでこれらを一緒にコミットできます。LinkedInが書いたHDFSローダーCamusは、Hadoopロードに対してこのようなことを行います。トピック/パーティション/オフセットの組み合わせを使用して重複排除します。

これをもっと簡単にする2つの改善があると思います:

  • 必要に応じてサーバー上のサポートを統合することにより、プロデューサーのdem等性を自動的に、はるかに安価に実行できます。
  • 既存の高レベルコンシューマーは、オフセットのよりきめ細かな制御の多くを公開しません(たとえば、位置をリセットするため)。私たちはすぐにそれに取り組んでいます
26
RaGe

消費者側でのRaGeの重複排除に同意します。そして、Redisを使用してKafka=メッセージを重複排除します。

Messageクラスに「uniqId」というメンバーがあり、プロデューサー側によって満たされ、一意であることが保証されていると仮定します。 12の長さのランダム文字列を使用します。 (正規表現は'^[A-Za-z0-9]{12}$'

コンシューマー側は、RedisのSETNXを使用して重複を排除し、期限切れのキーを自動的に削除するためにEXPIREします。サンプルコード:

Message msg = ... // eg. ConsumerIterator.next().message().fromJson();
Jedis jedis = ... // eg. JedisPool.getResource();
String key = "SPOUT:" + msg.uniqId; // prefix name at will
String val = Long.toString(System.currentTimeMillis());
long rsps = jedis.setnx(key, val);
if (rsps <= 0) {
    log.warn("kafka dup: {}", msg.toJson()); // and other logic
} else {
    jedis.expire(key, 7200); // 2 hours is ok for production environment;
}

上記のコードは、Kafka(バージョン0.8.x)に状況がある場合に重複メッセージを数回検出しました。入出力バランス監査ログを使用すると、メッセージの損失や重複は発生しませんでした。

16
peihan

プロデューサー側で何をしたとしても、それでもkafka=

  1. KafkaメッセージとしてUUIDを使用してmsgを生成
  2. コンシューマー側はT1からメッセージを読み取り、行キーとしてuuidを使用してhbaseに書き込みます
  3. 同じ行キーでhbaseから読み戻し、別のトピックT2に書き込みます
  4. 最終消費者にトピックT2から実際に消費させる
0
Dean Jain