web-dev-qa-db-ja.com

Kafka-高レベルのコンシューマを使用した遅延キューの実装

高レベルのコンシューマーAPIを使用して遅延コンシューマーを実装したい

本旨:

  • キーごとにメッセージを生成します(各msgには作成タイムスタンプが含まれます)。これにより、各パーティションが生成された時間順にメッセージを順序付けします。
  • auto.commit.enable = false(各メ​​ッセージプロセスの後に明示的にコミットします)
  • メッセージを消費する
  • メッセージのタイムスタンプを確認し、十分な時間が経過したかどうかを確認します
  • プロセスメッセージ(この操作は失敗しません)
  • コミット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. 各オフセットをコミットするとZKが遅くなる可能性があります
  2. consumer.commitOffsetsは例外をスローできますか?はいの場合、同じメッセージを2回消費します(べき等のメッセージで解決できます)
  3. オフセットをコミットせずに長時間待機する問題、たとえば遅延時間が24時間である場合、イテレータから次に取得し、24時間スリープし、処理してコミットします(ZKセッションタイムアウト?)
  4. 新しいオフセットをコミットせずにZKセッションをキープアライブするにはどうすればよいですか? (Hiveのzookeeper.session.timeout.msを設定すると、死んだコンシューマーでそれを認識せずに解決できます)
  5. その他の問題はありませんか?

ありがとう!

18
Nimrod007

これを解決する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に戻します。

特定の質問に答えるために(質問へのコメントに記載されているものもあります)

  1. 各オフセットをコミットするとZKが遅くなる可能性があります

Kafka(0.8.2から利用可能な機能、チェックアウトoffsets.storageコンシューマー構成のプロパティ)

  1. consumer.commitOffsetsは例外をスローできますか?はいの場合、同じメッセージを2回消費します(べき等のメッセージで解決できます)

たとえば、オフセットストレージと通信できない場合は可能だと思います。あなたが言うように、dem等メッセージを使用すると、この問題を解決できます。

  1. オフセットをコミットせずに長時間待機する問題、たとえば遅延時間が24時間である場合、イテレータから次に取得し、24時間スリープし、処理してコミットします(ZKセッションタイムアウト?)

これは、メッセージ自体の処理にセッションタイムアウト以上の時間がかかる場合を除き、上記のソリューションでは問題になりません。

  1. 新しいオフセットをコミットせずにZKセッションをキープアライブするにはどうすればよいですか? (Hiveのzookeeper.session.timeout.msを設定すると、死んだコンシューマーでそれを認識せずに解決できます)

この場合も、長いセッションタイムアウトを設定する必要はありません。

  1. その他の問題はありませんか?

常にあります;)

18
Emil H

あなたの場合は別のルートをお勧めします。

消費者のメインスレッドで待機時間に対処することは意味がありません。これは、キューの使用方法のアンチパターンになります。概念的には、メッセージをできるだけ速く処理し、キューを低い負荷係数に保つ必要があります。

代わりに、遅延する必要がある各メッセージのジョブをスケジュールするスケジューラーを使用します。このようにして、キューを処理し、事前定義された時点でトリガーされる非同期ジョブを作成できます。

この手法を使用することの欠点は、スケジュールされたジョブをメモリに保持するJVMのステータスに依存することです。そのJVMが失敗すると、スケジュールされたジョブが失われ、タスクが実行されたかどうかがわかりません。

スケジューラー実装がありますが、クラスター環境で実行するように構成できるため、JVMクラッシュから安全に保つことができます。

これを見てくださいJavaスケジューリングフレームワーク: http://www.quartz-scheduler.org/

2
nucatus

Tibco EMSまたは他のJMSキューを使用します。それらには再試行遅延が組み込まれています。 Kafkaは、あなたがしていることに対して正しい設計選択ではないかもしれません

1
Dhyan

スケジュールに基づいたキー付きリストまたはそのredisの代替案が最善のアプローチかもしれません。

0
softwarevamp