web-dev-qa-db-fra.com

Kafka - Mise en œuvre de la file d'attente retardée à l'aide d'un consommateur de haut niveau

Voulez-vous mettre en œuvre un consommateur retardé en utilisant le haut niveau de l'API des consommateurs 

idée principale:

  • produire des messages par clé (chaque msg contient un horodatage de création), ce qui permet de s'assurer que chaque partition a classé les messages par heure produite.
  • auto.commit.enable = false (sera explicitement validé après chaque traitement de message)
  • consommer un message
  • vérifier l'horodatage du message et vérifier s'il s'est écoulé suffisamment de temps
  • message de processus (cette opération n'échouera jamais)
  • commettre 1 offset

    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
    }
    

quelques inquiétudes à propos de cette implémentation:

  1. commettre chaque décalage pourrait ralentir ZK
  2. consumer.commitOffsets peut-il lever une exception? si oui, je consommerai le même message deux fois (je peux le résoudre avec des messages idempotents)
  3. problème attendre longtemps sans commettre l'offset, par exemple le délai est de 24 heures, suivra de l'itérateur, dormira pendant 24 heures, traitera et validera (délai de session ZK?)
  4. comment une session ZK peut-elle rester en vie sans commettre de nouveaux décalages? (définir un Hive zookeeper.session.timeout.ms peut résoudre un consommateur mort sans le reconnaître)
  5. d'autres problèmes im manquant?

Merci!

15
Nimrod007

Une façon de procéder consiste à utiliser un sujet différent dans lequel vous envoyez tous les messages à retarder. Si tous les messages retardés doivent être traités après le même délai, ce sera assez simple:

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

Tous les messages normaux seront désormais traités dès que possible, tandis que ceux qui ont besoin d'un délai sont mis sur un autre sujet. 

La bonne chose est que nous savons que le message en tête du sujet en retard est celui qui doit être traité en premier car sa valeur delayTo sera la plus petite. Par conséquent, nous pouvons configurer un autre consommateur qui lit le message principal, vérifie si l'horodatage est dans le passé et, si tel est le cas, traite le message et valide le décalage. Sinon, il ne commet pas le décalage et se met en veille jusqu'à ce moment-là:

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

Si le temps de retard est différent, vous pouvez répartir le sujet sur le retard (par exemple, 24 heures, 12 heures, 6 heures). Si le temps de retard est plus dynamique que cela, il devient un peu plus complexe. Vous pouvez le résoudre en introduisant deux sujets de délai. Lisez tous les messages hors sujet A et traitez tous les messages dont la valeur delayTo est passée. Parmi les autres, vous trouvez simplement celui qui a la delayTo la plus proche et vous les mettez ensuite dans le sujet B. Dormez jusqu’à ce que le plus proche soit traité et faites tout en sens inverse, c’est-à-dire traitez les messages du sujet B et mettez l’une fois qui ne devrait pas encore être traité sur le sujet A.

Pour répondre à vos questions spécifiques (certaines ont été abordées dans les commentaires de votre question)

  1. commettre chaque décalage pourrait ralentir ZK

Vous pouvez envisager de passer au stockage de l'offset dans Kafka (fonctionnalité disponible à partir de la version 0.8.2, consultez la propriété offsets.storage dans la configuration client).

  1. consumer.commitOffsets peut-il lever une exception? si oui, je consommerai le même message deux fois (je peux le résoudre avec des messages idempotents)

Je crois que c'est possible si, par exemple, il ne peut pas communiquer avec la mémoire de décalage. L'utilisation de messages idempotents résout ce problème, comme tu le dis.

  1. problème attendre longtemps sans commettre l'offset, par exemple le délai est de 24 heures, suivra de l'itérateur, dormira pendant 24 heures, traitera et validera (délai de session ZK?)

Ce ne sera pas un problème avec la solution décrite ci-dessus à moins que le traitement du message lui-même ne prenne plus que le délai d'attente de la session.

  1. comment une session ZK peut-elle rester en vie sans commettre de nouveaux décalages? (définir un Hive zookeeper.session.timeout.ms peut résoudre un consommateur mort sans le reconnaître)

Encore une fois, avec ce qui précède, vous ne devriez pas avoir besoin de définir un délai d’expiration de session long.

  1. d'autres problèmes im manquant?

Il y en a toujours;)

15
Emil H

Je suggérerais un autre itinéraire dans vos cas. 

Cela n'a pas de sens de traiter le temps d'attente dans le fil conducteur du consommateur. Ce sera un anti-modèle dans la façon dont les files d'attente sont utilisées. Sur le plan conceptuel, vous devez traiter les messages le plus rapidement possible et conserver la file d’attente à un facteur de charge faible.

Au lieu de cela, je voudrais utiliser un planificateur qui planifiera les travaux pour chaque message que vous devez retarder. De cette façon, vous pouvez traiter la file d'attente et créer des travaux asynchrones qui seront déclenchés à des moments prédéfinis. 

L'utilisation de cette technique a pour inconvénient le fait qu'elle est sensible au statut de la machine virtuelle Java qui contient les travaux planifiés en mémoire. Si cette machine virtuelle Java échoue, vous perdez les travaux planifiés et vous ne savez pas si la tâche a été exécutée ou non. 

Il existe des implémentations de planificateur, qui peuvent toutefois être configurées pour s'exécuter dans un environnement de cluster, vous protégeant ainsi des blocages de la machine virtuelle Java. 

Jetez un coup d’œil à ce framework de planification Java: http://www.quartz-scheduler.org/

2
nucatus

Utilisez Tibco EMS ou une autre file d’attente JMS. Ils ont intégré le délai avant nouvelle tentative. Kafka n'est peut-être pas le bon choix de design pour ce que vous faites

1
Dhyan

Une liste saisie au calendrier ou son alternative redis peuvent être les meilleures approches.

0
softwarevamp