如何使用spring-kafka再次发送消息

时间:2018-12-04 22:32:46

标签: spring-kafka

我们正在使用spring-kafka 1.2.2.RELEASE。

我们想要的
1.一旦消息被成功使用和处理,就会在spring-kafka中提交offset。
我正在使用Manaul Commit / Acknowledgement,它工作正常。

2.如有任何例外,我们希望spring-kafka重新发送相同的消息。
我们会在任何系统错误上引发RunTime异常,该异常由spring-kafka记录,从未提交。 这很好,因为我们不希望其提交,但是该消息保留在spring-kafka中,除非重新启动服务,否则它永远不会回来。在重新启动消息返回并再次执行,然后停留在spring-kafka

我们尝试过的事情
1.我已经尝试了ErrorHandler和RetryingMessageListenerAdapter,但是在两种情况下,我们都必须在服务中编写如何再次处理消息的代码

这是我的消费者

public class MyConsumer{
    @KafkaListener
    public void receive(...){
        // application logic to return success/failure
        if(success){
            acknowledgement.acknowledge();
        }else{
            throw new RunTimeException();
        }
    }
} 

我对容器工厂也有以下配置

factory.getContainerProperties().setErrorHandler(new ErrorHandler(){
    @Override
    public void handle(...){
        throw new RunTimeException("");
    }
});

在执行流程时,控制权先进入内部,然后接收并处理方法。之后,该服务等待新消息。但是,我期望如此,因为我们引发了异常,并且未提交消息,因此同一条消息应该再次进入接收方法。

有什么办法,我们可以告诉spring kafka“不要提交此消息并尽快将其再次发送?”

2 个答案:

答案 0 :(得分:1)

1.2.x不再受支持;由于KIP-62,由于1.x用户的线程模型更加简单,因此建议1.x用户至少升级到1.3.x(当前为1.3.8)。

当前版本为2.2.2。

2.0.1引入了SeekToCurrentErrorHandler,它可以重新查找失败的记录,以便重新发送。

对于早期版本,您必须停止并重新启动容器才能重新发送失败的消息,或者将重试添加到侦听器适配器。

我建议您升级到最新版本。

答案 1 :(得分:0)

很遗憾,我们可以使用的版本是1.3.7.RELEASE。

我尝试实现ConsumerSeekAware接口。下面是我的操作方式,可以看到邮件重新发送了

消费者

public class MyConsumer implements ConsumerSeekAware{
    private ConsumerSeekCallback consumerSeekCallback;
    if(condition) {
            acknowledgement.acknowledge();
        }else {
            consumerSeekCallback.seek((String)headers.get("kafka_receivedTopic"),
                    (int) headers.get("kafka_receivedPartitionId"),
                    (int) headers.get("kafka_offset"));
        }
    }

    @Override
    public void registerSeekCallback(ConsumerSeekCallback consumerSeekCallback) {
        this.consumerSeekCallback = consumerSeekCallback;
    }

    @Override
    public void onIdleContainer(Map<TopicPartition, Long> arg0, ConsumerSeekCallback arg1) {
        LOGGER.debug("onIdleContainer called");
    }

    @Override
    public void onPartitionsAssigned(Map<TopicPartition, Long> arg0, ConsumerSeekCallback arg1) {
        LOGGER.debug("onPartitionsAssigned called");
    }
}

配置

public class MyConsumerConfig {

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        // Set server, deserializer, group id
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        return props;
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, MyModel> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, MyModel> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerConfigs()));
        factory.getContainerProperties().setAckMode(AckMode.MANUAL);
        return factory;
    }

    @Bean
    public MyConsumer receiver() {
        return new MyConsumer();
    }
}