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:
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é:
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é).
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.