高レベルのコンシューマーAPIを使用して遅延コンシューマーを実装したい
本旨:
コミット1オフセット
while (it.hasNext()) {
val msg = it.next().message()
//checks timestamp in msg to see delay period exceeded
while (!delayedPeriodPassed(msg)) {
waitSomeTime() //Thread.sleep or something....
}
//certain that the msg was delayed and can now be handled
Try { process(msg) } //the msg process will never fail the consumer
consumer.commitOffsets //commit each msg
}
この実装に関するいくつかの懸念:
ありがとう!
これを解決する1つの方法は、遅延させるすべてのメッセージをプッシュする別のトピックを使用することです。すべての遅延メッセージを同じ時間遅延後に処理する必要がある場合、これはかなり簡単です。
while(it.hasNext()) {
val message = it.next().message()
if(shouldBeDelayed(message)) {
val delay = 24 hours
val delayTo = getCurrentTime() + delay
putMessageOnDelayedQueue(message, delay, delayTo)
}
else {
process(message)
}
consumer.commitOffset()
}
すべての通常のメッセージはできるだけ早く処理されるようになり、遅延が必要なメッセージは別のトピックに配置されます。
良いことは、delayTo値が最小になるため、遅延トピックの先頭にあるメッセージが最初に処理されるべきであることを知っていることです。したがって、先頭のメッセージを読み取り、タイムスタンプが過去のものかどうかを確認し、そうである場合はメッセージを処理してオフセットをコミットする別のコンシューマを設定できます。そうでない場合は、オフセットをコミットせず、代わりにその時間までスリープします。
while(it.hasNext()) {
val delayedMessage = it.peek().message()
if(delayedMessage.delayTo < getCurrentTime()) {
val readMessage = it.next().message
process(readMessage.originalMessage)
consumer.commitOffset()
} else {
delayProcessingUntil(delayedMessage.delayTo)
}
}
遅延時間が異なる場合は、遅延でトピックを分割できます(24時間、12時間、6時間など)。遅延時間がそれより動的である場合、少し複雑になります。 2つの遅延トピックを導入することで解決できます。遅延トピックA
からすべてのメッセージを読み取り、delayTo
値が過去にあるすべてのメッセージを処理します。とりわけ、最も近いdelayTo
を持つものを見つけて、トピックB
に配置します。最も近いものが処理されるまでスリープし、それをすべて逆に実行します。つまり、トピックB
からのメッセージを処理し、まだ処理されないはずの1回をトピックA
に戻します。
特定の質問に答えるために(質問へのコメントに記載されているものもあります)
- 各オフセットをコミットするとZKが遅くなる可能性があります
Kafka(0.8.2から利用可能な機能、チェックアウトoffsets.storage
コンシューマー構成のプロパティ)
- consumer.commitOffsetsは例外をスローできますか?はいの場合、同じメッセージを2回消費します(べき等のメッセージで解決できます)
たとえば、オフセットストレージと通信できない場合は可能だと思います。あなたが言うように、dem等メッセージを使用すると、この問題を解決できます。
- オフセットをコミットせずに長時間待機する問題、たとえば遅延時間が24時間である場合、イテレータから次に取得し、24時間スリープし、処理してコミットします(ZKセッションタイムアウト?)
これは、メッセージ自体の処理にセッションタイムアウト以上の時間がかかる場合を除き、上記のソリューションでは問題になりません。
- 新しいオフセットをコミットせずにZKセッションをキープアライブするにはどうすればよいですか? (Hiveのzookeeper.session.timeout.msを設定すると、死んだコンシューマーでそれを認識せずに解決できます)
この場合も、長いセッションタイムアウトを設定する必要はありません。
- その他の問題はありませんか?
常にあります;)
あなたの場合は別のルートをお勧めします。
消費者のメインスレッドで待機時間に対処することは意味がありません。これは、キューの使用方法のアンチパターンになります。概念的には、メッセージをできるだけ速く処理し、キューを低い負荷係数に保つ必要があります。
代わりに、遅延する必要がある各メッセージのジョブをスケジュールするスケジューラーを使用します。このようにして、キューを処理し、事前定義された時点でトリガーされる非同期ジョブを作成できます。
この手法を使用することの欠点は、スケジュールされたジョブをメモリに保持するJVMのステータスに依存することです。そのJVMが失敗すると、スケジュールされたジョブが失われ、タスクが実行されたかどうかがわかりません。
スケジューラー実装がありますが、クラスター環境で実行するように構成できるため、JVMクラッシュから安全に保つことができます。
これを見てくださいJavaスケジューリングフレームワーク: http://www.quartz-scheduler.org/
Tibco EMSまたは他のJMSキューを使用します。それらには再試行遅延が組み込まれています。 Kafkaは、あなたがしていることに対して正しい設計選択ではないかもしれません
スケジュールに基づいたキー付きリストまたはそのredisの代替案が最善のアプローチかもしれません。