web-dev-qa-db-de.com

Kafka - Implementierung der verzögerten Warteschlange unter Verwendung hoher Konsumenten

Sie möchten einen verzögerten Konsumenten implementieren, indem Sie die High-End-Consumer-API verwenden 

hauptidee:

  • nachrichten nach Schlüssel erzeugen (jede Nachricht enthält einen Zeitstempel für die Erstellung) Dies stellt sicher, dass jede Partition Nachrichten nach Produktionszeit geordnet hat.
  • auto.commit.enable = false (wird nach jedem Nachrichtenprozess explizit festgeschrieben)
  • verbrauchen Sie eine Nachricht
  • Überprüfen Sie den Zeitstempel der Nachricht und prüfen Sie, ob genügend Zeit vergangen ist
  • prozessnachricht (dieser Vorgang wird niemals fehlschlagen)
  • 1 Offset übernehmen

    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
    }
    

einige Bedenken bezüglich dieser Implementierung:

  1. commit jeder Offset könnte ZK verlangsamen
  2. kann consumer.commitOffsets eine Ausnahme auslösen? Wenn ja, werde ich dieselbe Nachricht zweimal konsumieren (kann mit idempotenten Nachrichten lösen)
  3. problem lange Wartezeit, ohne den Versatz zu bestätigen, beispielsweise ist die Verzögerungszeit 24 Stunden, wird als nächstes vom Iterator kommen, 24 Stunden lang schlafen, verarbeiten und festschreiben (Zeitlimit für ZK-Sitzung?)
  4. wie kann die ZK-Session am Leben bleiben, ohne neue Offsets einzugehen? (Das Setzen eines Hive zookeeper.session.timeout.ms kann in toten Verbrauchern ohne Erkennen aufgelöst werden.)
  5. irgendwelche anderen Probleme, die mir fehlen?

Vielen Dank!

15
Nimrod007

Um dies zu erreichen, können Sie ein anderes Thema verwenden, in dem Sie alle Nachrichten pushen, die verzögert werden sollen. Wenn alle verzögerten Nachrichten nach derselben Zeitverzögerung verarbeitet werden sollen, ist dies recht einfach:

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()
}

Alle regulären Nachrichten werden jetzt so schnell wie möglich verarbeitet, während diejenigen, die eine Verzögerung benötigen, ein anderes Thema erhalten. 

Das Schöne ist, dass wir wissen, dass die Nachricht an der Spitze des verzögerten Themas diejenige ist, die zuerst verarbeitet werden sollte, da der delayTo-Wert der kleinste ist. Daher können wir einen anderen Consumer einrichten, der die Kopfnachricht liest, prüft, ob der Zeitstempel in der Vergangenheit liegt, und wenn ja, verarbeitet er die Nachricht und schreibt den Offset ein. Wenn dies nicht der Fall ist, wird der Versatz nicht festgeschrieben und stattdessen bis zu diesem Zeitpunkt in den Ruhezustand versetzt:

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)
    }
}

Falls es unterschiedliche Verzögerungszeiten gibt, können Sie das Thema in die Verzögerung einteilen (z. B. 24 Stunden, 12 Stunden, 6 Stunden). Wenn die Verzögerungszeit dynamischer ist, wird sie etwas komplexer. Sie könnten es lösen, indem Sie zwei Verzögerungsthemen einführen. Lesen Sie alle Nachrichten mit Verzögerungsthema A und verarbeiten Sie alle Nachrichten, deren delayTo-Wert in der Vergangenheit liegt. Unter den anderen finden Sie nur diejenige mit der nächsten delayTo und setzen sie dann auf das Thema B. Schlaf, bis der nächstliegende verarbeitet werden sollte, und tun Sie alles in umgekehrter Reihenfolge, d. H. Nachrichten vom Thema B verarbeiten, und setzen Sie das einmalige Ereignis, das noch nicht verarbeitet werden sollte, auf das Thema A.

Um Ihre spezifischen Fragen zu beantworten (einige wurden in den Kommentaren zu Ihrer Frage angesprochen)

  1. commit jeder Offset könnte ZK verlangsamen

Sie könnten in Erwägung ziehen, den Versatz in Kafka zu speichern (eine Funktion, die ab 0.8.2 verfügbar ist, überprüfen Sie die offsets.storage-Eigenschaft in der Consumer-Konfiguration).

  1. kann consumer.commitOffsets eine Ausnahme auslösen? Wenn ja, werde ich dieselbe Nachricht zweimal konsumieren (kann mit idempotenten Nachrichten lösen)

Ich glaube, dass es möglich ist, wenn es beispielsweise nicht mit dem Offset-Speicher kommunizieren kann. Mit idempotenten Nachrichten lösen Sie dieses Problem, wie Sie sagen.

  1. problem lange Wartezeit, ohne den Versatz zu bestätigen, beispielsweise ist die Verzögerungszeit 24 Stunden, wird als nächstes vom Iterator kommen, 24 Stunden lang schlafen, verarbeiten und festschreiben (Zeitlimit für ZK-Sitzung?)

Dies ist bei der oben beschriebenen Lösung kein Problem, es sei denn, die Verarbeitung der Nachricht selbst beansprucht mehr als das Sitzungszeitlimit.

  1. wie kann die ZK-Session am Leben bleiben, ohne neue Offsets einzugehen? (Das Setzen eines Hive zookeeper.session.timeout.ms kann in toten Verbrauchern ohne Erkennen aufgelöst werden.)

Mit den obigen Angaben sollten Sie auch keine lange Sitzungszeitüberschreitung festlegen.

  1. irgendwelche anderen Probleme, die mir fehlen?

Es gibt immer;)

15
Emil H

Ich würde in Ihren Fällen eine andere Route vorschlagen. 

Es macht keinen Sinn, die Wartezeit im Hauptthread des Konsumenten anzusprechen. Dies wird ein Anti-Muster in der Verwendung der Warteschlangen sein. Konzeptionell müssen Sie die Nachrichten so schnell wie möglich verarbeiten und die Warteschlange bei einem niedrigen Ladefaktor halten.

Stattdessen würde ich einen Scheduler verwenden, der Jobs für jede Nachricht plant, die Sie verzögern müssen. Auf diese Weise können Sie die Warteschlange verarbeiten und asynchrone Jobs erstellen, die zu vordefinierten Zeitpunkten ausgelöst werden. 

Die Verwendung dieser Technik bringt es mit sich, dass der Status der JVM, die die geplanten Jobs im Speicher hält, sinnvoll ist. Wenn diese JVM fehlschlägt, verlieren Sie die geplanten Aufträge und wissen nicht, ob die Aufgabe ausgeführt wurde oder nicht. 

Es gibt Scheduler-Implementierungen, die jedoch für die Ausführung in einer Cluster-Umgebung konfiguriert werden können, sodass Sie vor Abstürzen der JVM geschützt sind. 

Sehen Sie sich dieses Java-Scheduling-Framework an: http://www.quartz-scheduler.org/

2
nucatus

Verwenden Sie Tibco EMS oder andere JMS-Warteschlangen. Sie haben eine Wiederholungsverzögerung eingebaut. Kafka ist möglicherweise nicht die richtige Wahl für das, was Sie tun

1
Dhyan

Die zeitlich festgelegte Schlüsselliste oder ihre Neuwahlalternative sind möglicherweise die besten Ansätze.

0
softwarevamp