簡體   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