分散環境では、障害はいつでも発生する非常に一般的なシナリオです。Kafka環境では、ブローカーがクラッシュしたり、ネットワーク障害、処理の失敗、メッセージの発行中の失敗、メッセージのコンシュームの失敗などが発生する可能性があります。これらの異なるシナリオにより、異なる種類のデータ損失と重複が発生しました。
障害シナリオ
A(Ack失敗):プロデューサーは再試行> 1でメッセージを正常に発行しましたが、失敗したため確認を受信できませんでした。その場合、プロデューサーは同じメッセージを再試行し、重複が発生する可能性があります。
B(プロデューサープロセスはバッチメッセージで失敗しました):プロデューサーが失敗したメッセージのバッチを送信し、公開された成功はほとんどありませんでした。その場合、プロデューサーが再起動すると、バッチからすべてのメッセージが再度再発行され、Kafkaで重複が発生します。
C(Fire&Forget Failed)プロデューサーは、retry = 0(fire and forget)でメッセージを公開しました。公開に失敗した場合、認識せずに次のメッセージを送信します。これによりメッセージが失われます。
D(コンシューマーはバッチメッセージで失敗しました)コンシューマーはKafkaからメッセージのバッチを受信し、オフセットを手動でコミットします(enable.auto.commit = false)。Kafkaにコミットする前にコンシューマーが失敗した場合、次にコンシューマーは同じレコードを再度消費し、コンシューマー側で複製を再現します。
1回限りのセマンティクス
この場合、プロデューサーがメッセージを再送信しようとしても、メッセージがパブリッシュされ、コンシューマーによって1回だけ消費されます。
KafkaでExactly-Onceセマンティックを実現するために、以下の3つのプロパティを使用します
- enable.idempotence = true(アドレスa、b、c)
- MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION = 5(プロデューサーは、接続ごとに常に1つの処理中の要求を持ちます)
- isolation.level = read_committed(アドレスd)
Idempotentを有効にする(enable.idempotence = true)
べき等配信により、プロデューサーは、単一のプロデューサーの存続期間中に、データ損失やパーティションごとの順序なしに、トピックの特定のパーティションにメッセージを1回だけKafkaに書き込むことができます。
「べき等を有効にするには、MAX_IN_FLIGHT_REQUESTS_PER_CONNECTIONが5以下である必要があることに注意してください。RETRIES_CONFIGは0より大きく、ACKS_CONFIGは「すべて」です。これらの値がユーザーによって明示的に設定されていない場合、適切な値が選択されます。互換性のない値の場合は、設定すると、ConfigExceptionがスローされます」
べき等性を実現するために、Kafkaはメッセージの生成中に製品IDまたはPIDと呼ばれる一意のIDとシーケンス番号を使用します。プロデューサーは、一意のPIDでマップされた、発行された各メッセージのシーケンス番号を増やし続けます。ブローカーは常に現在のシーケンス番号を以前のシーケンス番号と比較し、新しいシーケンス番号が以前のシーケンス番号より+1でない場合は拒否します。これにより、重複が回避され、メッセージの表示が失われた場合に同じ時間が繰り返されます。
失敗のシナリオでは、ブローカーはシーケンス番号を以前のものと比較し、シーケンスが増加していない場合、+ 1はメッセージを拒否します。
トランザクション(isolation.level)
トランザクションは、複数のトピックパーティションのデータをアトミックに更新する機能を提供します。トランザクションに含まれるすべてのレコードは正常に保存されるか、まったく保存されません。これにより、処理したデータとともに同じトランザクションでコンシューマーオフセットをコミットできるため、エンドツーエンドの正確に1回限りのセマンティクスが可能になります。
プロデューサーは、kafkaへのメッセージの書き込みを待機しません。プロデューサーは、beginTransaction、commitTransaction、およびabortTransactionを使用します(失敗した場合)。コンシューマーは、isolation.levelを使用します。read_committedまたはread_uncommitted
- read_committed:コンシューマーは常にコミットされたデータのみを読み取ります。
- read_uncommitted:トランザクションがコミットされるのを待たずに、オフセット順にすべてのメッセージを読み取ります
isolation.level = read_committedのコンシューマが完了していないトランザクションの制御メッセージに到達すると、プロデューサがトランザクションをコミットまたはアボートするか、トランザクションのタイムアウトが発生するまで、このパーティションからメッセージは配信されません。トランザクションのタイムアウトは、transaction.timeout.ms(デフォルトは1分)の設定を使用してプロデューサーが決定します。
プロデューサーとコンシューマーで1回だけ
生産者と消費者が分かれている通常の状態。プロデューサーは、べき等であると同時にトランザクションを管理する必要があるため、コンシューマーは、isolation.levelを使用してread_committedのみを読み取り、プロセス全体をアトミック操作として実行できます。これにより、プロデューサーが常にソースシステムと同期することが保証されます。プロデューサーのクラッシュやトランザクションの中止でも、常に一貫しており、メッセージまたはメッセージのバッチを1つの単位として1回公開します。
同じコンシューマは、メッセージまたはメッセージのバッチを1つの単位として1回受信します。
Exactly-Onceでは、ConsumerとともにSemantic Producerが1つのユニットとして動作するアトミック操作として表示されます。パブリッシュして一度に消費されるか、中止されます。
カフカストリームで1回のみ
Kafka StreamはトピックAからのメッセージを消費し、メッセージを処理してトピックBにパブリッシュします。パブリッシュしたら、commit(コミットはほとんどカバーなしで実行)を使用して、すべてのステートストアデータをディスクにフラッシュします。
Kafkaストリームで1回だけは、これらの操作がアトミック操作として扱われることを保証する読み取りプロセス書き込みパターンです。Kafkaストリームはプロデューサー、コンシューマー、トランザクションをすべて一緒に提供するので、Kafkaストリームは特別なパラメーターprocessing.guaranteeを提供します。これにより、すべてのパラメーターを個別に処理するのではなく、人生を簡単にします。
Kafka Streamsは、コンシューマーオフセット、ローカルステートストア、ステートストアの変更ログトピック、プロダクションをアトミックに更新し、トピックをまとめて出力します。これらのステップのいずれかが失敗すると、すべての変更がロールバックされます。
processing.guarantee:厳密に設定する必要のない以下のパラメーターをexact_onceが自動的に提供します
- isolation.level = read_committed
- enable.idempotence = true
- MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION = 5