卡夫卡与春天卡夫卡的死信队列(DLQ)

时间:2018-03-27 08:16:48

标签: spring-boot apache-kafka spring-integration spring-kafka dead-letter

使用spring-kafka 2.1.x在Spring Boot 2.0应用程序中实现死信队列(DLQ)概念的最佳方法是使所有消息都无法由处理@KafkaListener 某些bean的方法发送到某个预定义的Kafka DLQ主题而不会丢失单个消息?

消费的Kafka记录是:

  1. 已成功处理,
  2. 无法处理并被发送到DLQ主题
  3. 无法处理,未发送到DLQ主题(由于意外问题),因此将再次被侦听器使用。
  4. 我尝试使用 ErrorHandler 的自定义实现创建侦听器容器,使用KafkaTemplate发送无法处理到DLQ主题的记录。使用禁用自动提交和 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());
        }
    }
    

    此实施似乎并不保证项目#3 。如果在DlqErrorHandler中抛出异常,则侦听器不会再次使用该记录。

    使用事务监听器容器会有帮助吗?

    factory.getContainerProperties().setTransactionManager(kafkaTransactionManager);
    

    使用Spring Kafka有没有方便的方法来实现DLQ概念?

    更新28/03/2018

    感谢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);
            }
        }
    }
    

    这样,如果消费者民意调查返回3条记录(1,2,3),则第二条记录无法处理:

    • 1将被处理
    • 2将无法处理并发送至DLQ
    • 3感谢消费者寻求记录.offset()+ 1,它将被传递给听众

    如果发送到DLQ失败,则消费者会寻找record.offset(),并且记录将被重新传递给侦听器(并且发送到DLQ可能会被淘汰)。

1 个答案:

答案 0 :(得分:2)

请参阅SeekToCurrentErrorHandler

当发生异常时,它会寻找消费者,以便在下一轮调查中重新传递所有未处理的记录。

如果DLQ写入失败,您可以使用相同的技术(例如子类)写入DLQ并查找当前偏移量(以及其他未处理的偏移量),如果DLQ写入成功,则只搜索剩余记录。