web-dev-qa-db-fra.com

File d'attente de lettres mortes (DLQ) pour Kafka avec spring-kafka

Quelle est la meilleure façon d'implémenter le concept File d'attente de lettres mortes (DLQ) dans l'application Spring Boot 2.0 en utilisant spring-kafka 2.1.x pour que tous les messages qui n'ont pas pu être traités par @ KafkaListener méthode d'un bean envoyé à un prédéfini Kafka sujet DLQ et ne pas perdre le message unique?

Donc consommé Kafka record est soit:

  1. traité avec succès,
  2. n'a pas pu être traité et est envoyé à la rubrique DLQ,
  3. n'a pas pu être traité, n'est pas envoyé à la rubrique DLQ (en raison du problème inattendu) et sera donc à nouveau utilisé par l'auditeur.

J'ai essayé de créer un conteneur d'écoute avec l'implémentation personnalisée de ErrorHandler l'envoi des enregistrements n'a pas pu être traité vers la rubrique DLQ à l'aide de KafkaTemplate. Utilisation de la validation automatique désactivée et ENREGISTREMENT AckMode.

spring.kafka.enable-auto-ack=false
spring.kafka.listener.ack-mode=RECORD

@Configuration
public class KafkaConfig {
    @Bean
    ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<Integer, String> factory = ...
        ...
        factory.getContainerProperties().setErrorHandler(dlqErrorHandler);
        return factory;
    }
}

@Component
public class DlqErrorHandler implements ErrorHandler {

    @Autowired
    private KafkaTemplate<Object, Object> kafkaTemplate;

    @Value("${dlqTopic}")
    private String dlqTopic;

    @Override
    public void handle(Exception thrownException, ConsumerRecord<?, ?> record) {
        log.error("Error, sending to DLQ...");
        kafkaTemplate.send(dlqTopic, record.key(), record.value());
    }
}

Il semble que cette implémentation ne garantisse pas l'article #. Si une exception est levée dans l'enregistrement DlqErrorHandler, l'écouteur ne sera pas consommé à nouveau.

Est-ce que l'utilisation du conteneur d'écoute transactionnel sera utile?

factory.getContainerProperties().setTransactionManager(kafkaTransactionManager);

Existe-t-il un moyen pratique d'implémenter le concept DLQ à l'aide de Spring Kafka?

MISE À JOUR 28/03/2018

Grâce à la réponse de Gary Russell, j'ai pu obtenir le comportement souhaité en implémentant DlqErrorHandler comme suit

@Configuration
public class KafkaConfig {
    @Bean
    ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<Integer, String> factory = ...
        ...
        factory.getContainerProperties().setAckOnError(false);
        factory.getContainerProperties().setErrorHandler(dlqErrorHandler);
        return factory;
    }
}

@Component
public class DlqErrorHandler implements ContainerAwareErrorHandler {
    ...
    @Override
    public void handle(Exception thrownException, list<ConsumerRecord<?, ?> records, Consumer<?, ?> consumer, MessageListenerContainer container) {
        Consumerrecord<?, ? record = records.get(0);
        try {
            kafkaTemplate.send("dlqTopic", record.key, record.value());
            consumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset() + 1);
            // Other records may be from other partitions, so seek to current offset for other partitions too
            // ...
        } catch (Exception e) {
            consumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset());
            // Other records may be from other partitions, so seek to current offset for other partitions too
            // ...
            throw new KafkaException("Seek to current after exception", thrownException);
        }
    }
}

De cette façon, si le sondage auprès des consommateurs renvoie 3 enregistrements (1, 2, 3) et que le deuxième ne peut pas être traité:

  • 1 sera traité
  • 2 ne seront pas traités et envoyés au DLQ
  • 3 merci au consommateur de chercher à enregistrer.offset () + 1, il sera remis à l'auditeur

Si l'envoi vers DLQ échoue, le consommateur cherche dans record.offset () et l'enregistrement sera remis à l'auditeur (et l'envoi vers DLQ sera probablement retiré).

5
Evgeniy Khyst

Voir SeekToCurrentErrorHandler .

Lorsqu'une exception se produit, il recherche le consommateur afin que tous les enregistrements non traités soient redistribués lors du prochain sondage.

Vous pouvez utiliser la même technique (par exemple, une sous-classe) pour écrire dans le DLQ et rechercher le décalage actuel (et d'autres non traités) si l'écriture DLQ échoue, et rechercher uniquement les enregistrements restants si l'écriture DLQ réussit.

3
Gary Russell