Apache kafka 发生异常时,卡夫卡事件收到10次

Apache kafka 发生异常时,卡夫卡事件收到10次,apache-kafka,spring-kafka,Apache Kafka,Spring Kafka,我们的应用程序使用Spring Kafka,我与消费者之间存在问题 后端“生产者” 后端“消费者” 如果未抛出异常,则使用者将接收事件1次,但当从“doSomething”方法(RuntimeException)抛出异常时,我的使用者将接收事件10次,我不明白为什么 我尝试在我的使用者中使用retryPolicy设置重试模板,但没有任何更改: private RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new

我们的应用程序使用Spring Kafka,我与消费者之间存在问题

后端“生产者”

后端“消费者”

如果未抛出异常,则使用者将接收事件1次,但当从“doSomething”方法(RuntimeException)抛出异常时,我的使用者将接收事件10次,我不明白为什么

我尝试在我的使用者中使用retryPolicy设置重试模板,但没有任何更改:

private RetryTemplate retryTemplate() {
    RetryTemplate retryTemplate = new RetryTemplate();
    retryTemplate.setRetryPolicy(getSimpleRetryPolicy());
    return retryTemplate;
}

private SimpleRetryPolicy getSimpleRetryPolicy() {
    return new SimpleRetryPolicy(1);
}
这是我的消费者配置:

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

@Bean
public ConsumerFactory<String, DomainEvent> consumerFactory(@Autowired KafkaProperties kafkaProperties,
                                                            @Autowired KafkaConsumerCustomization consumerCustomization) {
    final JsonDeserializer<DomainEvent> jsonDeserializer = new JsonDeserializer<>();
    jsonDeserializer.getTypeMapper().setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence.TYPE_ID);
    jsonDeserializer.configure(Map.of(JsonDeserializer.TYPE_MAPPINGS, consumerCustomization.getFlatIdClassMapping()), false);
    jsonDeserializer.addTrustedPackages(consumerCustomization.getTrustedPackages());
    kafkaProperties.getProperties().put(ConsumerConfig.GROUP_ID_CONFIG, consumerCustomization.getConsumerId());
    kafkaProperties.getProperties().put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
    return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties(), new StringDeserializer(), jsonDeserializer);
}

@Bean
public ConcurrentKafkaListenerContainerFactory<String, DomainEvent> kafkaListenerContainerFactory(
        @Autowired ConsumerFactory<String, DomainEvent> consumerFactory) {
    ConcurrentKafkaListenerContainerFactory<String, DomainEvent> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
    factory.getContainerProperties().setAckOnError(false);
    factory.setRetryTemplate(retryTemplate());
    factory.getContainerProperties().setMissingTopicsFatal(false);
    factory.setConsumerFactory(consumerFactory);
    return factory;
}

private RetryTemplate retryTemplate() {
    RetryTemplate retryTemplate = new RetryTemplate();
    retryTemplate.setRetryPolicy(getSimpleRetryPolicy());
    return retryTemplate;
}

private SimpleRetryPolicy getSimpleRetryPolicy() {
    return new SimpleRetryPolicy(1);
}

public static class KafkaConsumerCustomization {

    private final String[] trustedPackages;
    private final Map<String, Class<?>> idClassMapping;
    private final String consumerId;

    public KafkaConsumerCustomization(String[] trustedPackages, final Map<String, Class<?>> idClassMapping, String consumerId) {
        this.trustedPackages = trustedPackages;
        this.idClassMapping = idClassMapping;
        this.consumerId = consumerId;
    }

    public String[] getTrustedPackages() {
        return trustedPackages;
    }

    public String getFlatIdClassMapping() {
        return idClassMapping.entrySet().stream()
                             .map(stringClassEntry -> stringClassEntry.getKey() + ":" + stringClassEntry.getValue().getCanonicalName())
                             .collect(Collectors.joining(","));
    }

    public Map<String, Class<?>> getIdClassMapping() {
        return idClassMapping;
    }

    public String getConsumerId() {
        return consumerId;
    }
}
@Configuration
@EnableKafka
public class KafkaProducerConfig {

    @Bean
    public KafkaTemplate<String, DomainEvent> kafkaTemplate(@Autowired final KafkaProperties kafkaProperties) {
        return new KafkaTemplate<>(this.producerFactory((kafkaProperties)));
    }

    @Bean
    public ProducerFactory<String, DomainEvent> producerFactory(final KafkaProperties kafkaProperties) {
        return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties(), new StringSerializer(), new JsonSerializer<>());
    }
}
@配置
@使能卡夫卡
公共类卡夫卡消费者配置{
@豆子
公共消费者工厂消费者工厂(@Autowired KafkaProperties KafkaProperties,
@自动连线卡夫卡消费者定制(消费者个性化){
最终JsonDeserializer JsonDeserializer=新JsonDeserializer();
jsonDeserializer.getTypeMapper().setTypePreference(Jackson2JavaTypeMapper.TypePreference.TYPE_ID);
configure(Map.of(jsondesellizer.TYPE_MAPPINGS,consumercUserMization.getFlatIdClassMapping()),false);
jsonDeserializer.addTrustedPackages(consumerCustomization.getTrustedPackages());
kafkaProperties.getProperties().put(ConsumerConfig.GROUP\u ID\u CONFIG,consumerCustomization.getconsumerrid());
kafkaProperties.getProperties().put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,“false”);
返回新的DefaultKafkaConsumerFactory(kafkaProperties.buildConsumerProperties()、新的StringDeserializer()、jsonDeserializer);
}
@豆子
公共并发kafkaListenerContainerFactory kafkaListenerContainerFactory(
@自动连线消费者工厂(消费者工厂){
ConcurrentKafkListenerContainerFactory=新ConcurrentKafkListenerContainerFactory();
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL\u立即);
factory.getContainerProperties().setAckoneError(false);
setRetryTemplate(retryTemplate());
factory.getContainerProperties().setMissingTopicsFatal(false);
setConsumerFactory(consumerFactory);
返回工厂;
}
私有RetryTemplate RetryTemplate(){
RetryTemplate RetryTemplate=新RetryTemplate();
setRetryPolicy(getSimpleRetryPolicy());
返回retryTemplate;
}
私有SimpleRetryPolicy getSimpleRetryPolicy(){
返回新的SimpleRetryPolicy(1);
}
公共静态类卡夫卡消费定制{
私有最终字符串[]trustedPackages;
私有最终映射>getIdClassMapping(){
返回idClassMapping;
}
公共字符串getConsumerId(){
回归消费;
}
}
}

和我的制作人配置:

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

@Bean
public ConsumerFactory<String, DomainEvent> consumerFactory(@Autowired KafkaProperties kafkaProperties,
                                                            @Autowired KafkaConsumerCustomization consumerCustomization) {
    final JsonDeserializer<DomainEvent> jsonDeserializer = new JsonDeserializer<>();
    jsonDeserializer.getTypeMapper().setTypePrecedence(Jackson2JavaTypeMapper.TypePrecedence.TYPE_ID);
    jsonDeserializer.configure(Map.of(JsonDeserializer.TYPE_MAPPINGS, consumerCustomization.getFlatIdClassMapping()), false);
    jsonDeserializer.addTrustedPackages(consumerCustomization.getTrustedPackages());
    kafkaProperties.getProperties().put(ConsumerConfig.GROUP_ID_CONFIG, consumerCustomization.getConsumerId());
    kafkaProperties.getProperties().put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
    return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties(), new StringDeserializer(), jsonDeserializer);
}

@Bean
public ConcurrentKafkaListenerContainerFactory<String, DomainEvent> kafkaListenerContainerFactory(
        @Autowired ConsumerFactory<String, DomainEvent> consumerFactory) {
    ConcurrentKafkaListenerContainerFactory<String, DomainEvent> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
    factory.getContainerProperties().setAckOnError(false);
    factory.setRetryTemplate(retryTemplate());
    factory.getContainerProperties().setMissingTopicsFatal(false);
    factory.setConsumerFactory(consumerFactory);
    return factory;
}

private RetryTemplate retryTemplate() {
    RetryTemplate retryTemplate = new RetryTemplate();
    retryTemplate.setRetryPolicy(getSimpleRetryPolicy());
    return retryTemplate;
}

private SimpleRetryPolicy getSimpleRetryPolicy() {
    return new SimpleRetryPolicy(1);
}

public static class KafkaConsumerCustomization {

    private final String[] trustedPackages;
    private final Map<String, Class<?>> idClassMapping;
    private final String consumerId;

    public KafkaConsumerCustomization(String[] trustedPackages, final Map<String, Class<?>> idClassMapping, String consumerId) {
        this.trustedPackages = trustedPackages;
        this.idClassMapping = idClassMapping;
        this.consumerId = consumerId;
    }

    public String[] getTrustedPackages() {
        return trustedPackages;
    }

    public String getFlatIdClassMapping() {
        return idClassMapping.entrySet().stream()
                             .map(stringClassEntry -> stringClassEntry.getKey() + ":" + stringClassEntry.getValue().getCanonicalName())
                             .collect(Collectors.joining(","));
    }

    public Map<String, Class<?>> getIdClassMapping() {
        return idClassMapping;
    }

    public String getConsumerId() {
        return consumerId;
    }
}
@Configuration
@EnableKafka
public class KafkaProducerConfig {

    @Bean
    public KafkaTemplate<String, DomainEvent> kafkaTemplate(@Autowired final KafkaProperties kafkaProperties) {
        return new KafkaTemplate<>(this.producerFactory((kafkaProperties)));
    }

    @Bean
    public ProducerFactory<String, DomainEvent> producerFactory(final KafkaProperties kafkaProperties) {
        return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties(), new StringSerializer(), new JsonSerializer<>());
    }
}
@配置
@使能卡夫卡
公共类KafkaProducerConfig{
@豆子
公共KafkatTemplate KafkatTemplate(@Autowired final KafkapProperties KafkapProperties){
返回新的卡夫卡模板(this.producerFactory((卡夫卡属性));
}
@豆子
公共生产工厂生产工厂(最终卡夫卡财产卡夫卡财产){
返回新的DefaultKafkaProducerFactory(kafkaProperties.buildProducerProperties()、新的StringSerializer()、新的JsonSerializer());
}
}

感谢您的帮助

,我建议您使用这些文档重试或跳过失败的记录

从版本2.2开始,SeektocurInterrorHandler现在可以恢复(跳过)一条不断失败的记录。默认情况下,10次失败后,将记录失败记录(错误)。您可以使用自定义恢复程序(BiConsumer)和/或max failures配置处理程序

和批处理侦听器

SeekToCurrentBatchErrorHandler将每个分区查找到批处理中每个分区的第一条记录,以便重放整个批处理。此错误处理程序不支持恢复,因为框架无法知道批处理中的哪个消息失败


我建议使用这些文档重试或跳过失败的记录

从版本2.2开始,SeektocurInterrorHandler现在可以恢复(跳过)一条不断失败的记录。默认情况下,10次失败后,将记录失败记录(错误)。您可以使用自定义恢复程序(BiConsumer)和/或max failures配置处理程序

和批处理侦听器

SeekToCurrentBatchErrorHandler将每个分区查找到批处理中每个分区的第一条记录,以便重放整个批处理。此错误处理程序不支持恢复,因为框架无法知道批处理中的哪个消息失败


这是正确的。从2.5版开始,默认的错误处理程序是
SeekToCurrentErrorHandler
,具有默认值(10次交付尝试,无退避)。是的,很管用!谢谢你的帮助!现在这件事过了我想要的次数。现在我还有另一个问题:如果我重新启动后端消费者,事件将再次发送。你知道为什么吗?这应该是另一个问题,但我相信这是一个偏移量提交问题,在你的应用程序终止@Cédricb之前,确保你的使用者是commit offset。实际上,以下代码解决了我的问题:errorHandler.setCommitRecovered(true);非常感谢。这是正确的。从2.5版开始,默认的错误处理程序是
SeekToCurrentErrorHandler
,具有默认值(10次交付尝试,无退避)。是的,很管用!谢谢你的帮助!现在这件事过了我想要的次数。现在我还有另一个问题:如果我重新启动后端消费者,事件将再次发送。你知道为什么吗?这应该是另一个问题,但我相信这是一个偏移量提交问题,在你的应用程序终止@Cédricb之前,确保你的使用者是commit offset。实际上,以下代码解决了我的问题:errorHandler.setCommitRecovered(true);非常感谢。
SeekToCurrentErrorHandler errorHandler =
new SeekToCurrentErrorHandler((record, exception) -> {
    // recover after 3 failures - e.g. send to a dead-letter topic
}, 3);