简体   繁体   English

Kafka的死信队列(DLQ)与spring-kafka

[英]Dead letter queue (DLQ) for Kafka with spring-kafka

What is the best way to implement Dead letter queue (DLQ) concept in Spring Boot 2.0 application using spring-kafka 2.1.x to have all messages that were failed to be processed by @KafkaListener method of some bean sent to some predefined Kafka DLQ topic and not lose the single message? 使用spring-kafka 2.1.x在Spring Boot 2.0应用程序中实现死信队列(DLQ)概念的最佳方法是将某些bean的@KafkaListener方法无法处理的所有消息发送到某个预定义的Kafka DLQ主题而不是丢失单个消息?

So consumed Kafka record is either: 因此消耗的Kafka记录是:

  1. successfully processed, 成功处理,
  2. failed to be processed and is sent to the DLQ topic, 无法处理并被发送到DLQ主题,
  3. failed to be processed, is not sent to the DLQ topic (due to the unexpected problem) so will be consumed by the listener again. 无法处理,未发送到DLQ主题(由于意外问题),因此将再次被侦听器使用。

I tried to create listener container with the custom implementation of the ErrorHandler sending records failed to be processed to DLQ topic using KafkaTemplate. 我尝试使用ErrorHandler的自定义实现创建侦听器容器,发送记录无法使用KafkaTemplate处理到DLQ主题。 Using disabled auto-commit and RECORD AckMode. 使用禁用的自动提交和RECORD 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());
    }
}

It seems that this implementation doesn't guarantee item #3 . 看来这个实现并不能保证第3项。 If an exception will be thrown in DlqErrorHandler record will not be consumed by the listener once again. 如果在DlqErrorHandler中抛出异常,则侦听器将不会再次使用该记录。

Will usage of the transactional listener container help? 使用事务监听器容器会有帮助吗?

factory.getContainerProperties().setTransactionManager(kafkaTransactionManager);

Is there any convenient way to implement DLQ concept using Spring Kafka? 有没有方便的方法来使用Spring Kafka实现DLQ概念?

UPDATE 28/03/2018 更新28/03/2018

Thanks to Gary Russell's answer I was able to achieve the desired behavior by implementing DlqErrorHandler as follows 感谢Gary Russell的回答,我能够通过实现DlqErrorHandler来实现所需的行为,如下所示

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

This way if consumer poll returns 3 records (1, 2, 3) and the 2nd one can't be processed: 这样,如果消费者轮询返回3条记录(1,2,3),则无法处理第二条记录:

  • 1 will be processed 1将被处理
  • 2 will fail to be processed and sent to the DLQ 2将无法处理并发送到DLQ
  • 3 thanks to consumer seek to record.offset() + 1, it will be delivered to the listener 3感谢消费者寻求记录.offset()+ 1,它将被传递给听众

If sending to DLQ fails consumer seeks to the record.offset() and the record will be re-delivered to the listener (and sending to DLQ probably will be retired). 如果发送到DLQ失败,则消费者寻找record.offset()并且记录将被重新传递给监听器(并且发送到DLQ可能将被淘汰)。

See the SeekToCurrentErrorHandler . 请参阅SeekToCurrentErrorHandler

When an exception occurs, it seeks the consumer so that all unprocessed records are redelivered on the next poll. 发生异常时,它会搜索消费者,以便在下次轮询时重新传递所有未处理的记录。

You can use the same technique (eg a subclass) to write to the DLQ and seek the current offset (and other unprocessed) if the DLQ write fails, and seek just the remaining records if the DLQ write succeeds. 如果DLQ写入失败,您可以使用相同的技术(例如子类)写入DLQ并查找当前偏移量(以及其他未处理的偏移量),并在DLQ写入成功时查找剩余记录。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM