繁体   English   中英

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

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

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

因此消耗的Kafka记录是:

  1. 成功处理,
  2. 无法处理并被发送到DLQ主题,
  3. 无法处理,未发送到DLQ主题(由于意外问题),因此将再次被侦听器使用。

我尝试使用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可能将被淘汰)。

请参阅SeekToCurrentErrorHandler

发生异常时,它会搜索消费者,以便在下次轮询时重新传递所有未处理的记录。

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

暂无
暂无

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

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