@KafkaListener不会读取以前的消息,即使它们没有被确认,除非应用程序重新启动

时间:2019-12-05 14:35:19

标签: java spring-boot apache-kafka

所以基本上,我所看到的是我正在使用@KafkaListener来消费消息,并且故意不希望确认它们,因此使用者将忽略它们并在主题中侦听新消息。因此,如果应用程序重新启动,则使用者仅从最后提交的消息开始。因此,我想避免这种情况,希望消费者在不重新启动的情况下考虑未提交的消息。下面是我正在使用的配置和@kafkalistener。

ReceiverConfig.java

package com.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.listener.ContainerProperties;

import java.util.HashMap;
import java.util.Map;

@EnableKafka
@Configuration
public class ReceiverConfig {

    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;

    @Value("${spring.kafka.group}")
    private String group;

    @Value("${spring.kafka.auto-offset-reset}")
    private String offset;

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, group);
        props.put(ConsumerConfig.CLIENT_ID_CONFIG, "CAS-CLIENT-001");
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
        props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "3000");
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, offset);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); //TODO Remove if you want Kafka to automatically acknowledge
        props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);
        props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 30000);
        return props;
    }

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs());
    }

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.setConcurrency(1);
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); //TODO Remove if you want Kafka to automatically acknowledge
        return factory;
    }
}

和ReceiverService

package com.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;

@Service
public class ReceiverService {

    private final Logger LOG = LoggerFactory.getLogger(ReceiverService.class);

    @KafkaListener(topics = "${spring.kafka.topic}")
    private synchronized void consumeKafkaQueue(@Payload String message, Acknowledgment acknowledgment) {
        LOG.info("Received message from kafka queue: {}", message);
        //TODO Do your code here...
        //acknowledgment.acknowledge();
        //Note if the "acknowledgment" parameter is left out Kafka will be acknowledged as soon as the method finishes.
    }
}

3 个答案:

答案 0 :(得分:0)

有什么价值?

@Value("${spring.kafka.auto-offset-reset}")
    private String offset;

here,您可以看到所有使用者配置

  

当Kafka中没有初始偏移量或服务器上不再存在当前偏移量时(例如因为该数据已被删除),该怎么办:

     

最早:将偏移量自动重置为最早的偏移量

     

最新:自动将偏移量重置为最新的偏移量

     

无:如果未找到消费者组的先前偏移量,则向消费者抛出异常

     

其他:向消费者抛出异常。

您应该最早使用

答案 1 :(得分:0)

注册回调时,您必须实现ConsumerSeekAware

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

此后,您可以调用就地搜索,而不是调用

if(...){
  acknowledgment.acknowledge();
}else{
  consumerSeekCallback.get().seek(consumerRecord.topic(), consumerRecord.partition(), consumerRecord.offset());
}

这是真的,因为spring使用内部批处理消息并将此代码用于检查确认:

protected void pollAndInvoke() {
  if (!this.autoCommit && !this.isRecordAck) {
    processCommits();
  }
    idleBetweenPollIfNecessary();
  if (this.seeks.size() > 0) {
    processSeeks();
  }
...

答案 2 :(得分:0)

即使在应用程序重新启动或以某种方式触发重新平衡之前,消费者也不会读取过去的几条消息,即使这些消息未提交。请参阅以下摘自 Kafka 权威指南。

重新平衡后,所有消费者将从上次提交的偏移量开始消费。

您可以通过连接几个消费者并删除一个来强制重新平衡。一旦超过默认的 10 秒限制,将触发重新平衡,您可以看到剩余的消费者将阅读这些消息。

而且这里没有 auto.offset.reset 属性的关系。

您只需按照此处的自述文件即可创建 Kafka 集群。

https://github.com/skshukla/infra