卡夫卡生产者和消费者问题

时间:2019-05-28 10:57:41

标签: java spring-boot apache-kafka spring-kafka

我对Apache Kafka有两个问题。

问题1

  1. 将100,000条消息推送到Kafka
  2. 在使用者使用Ctrl-C之前关闭Zookeeper和kafka服务 消耗了所有100,000条消息(通过模拟 Thread.sleep(1000)(在消耗方法中)。

发现

在Zookeeper和kafka服务关闭后,消费者继续在控制台上编写消息。

期望

在饲养员和卡夫卡长大后,消费者应停止使用消息,并从最后一条消息的索引+ 1重新开始。

问题

如何使使用者从上次使用的消息的索引+ 1开始继续

问题2

  1. 将100,000条消息推送到Kafka
  2. 在使用者使用Ctrl-C之前关闭Zookeeper和kafka服务 消耗了所有100,000条消息(通过模拟 Thread.sleep(1000)(在消耗方法中)。
  3. 杀死使用消息的Spring Boot应用程序
  4. 启动Zookeeper和kafka服务。
  5. 启动负责使用消息的Spring Boot应用程序。

发现

使用者从一开始就使用所有消息,而忽略了最后使用的消息。

期望

在关闭Spring Boot应用程序之前,使用者应从最后消耗的消息的索引+ 1开始消耗。

问题

如何使使用者从上次使用的消息的索引+ 1开始继续

代码段

KafkaConsumerConfig

@Configuration
@EnableKafka
public class KafkaConsumerConfig
{
    @Bean
    public Map<String, Object> consumerConfigs()
    {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "basic-group");
        return props;
    }

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

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>>
            kafkaListenerContainerFactory()
    {
        ConcurrentKafkaListenerContainerFactory<Integer, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.setConcurrency(3);
        factory.getContainerProperties().setPollTimeout(100);
        return factory;
    }
}

KafkaProducerConfig

@Configuration
    public class KafkaProducerConfig
    {
        @Bean
        public ProducerFactory<Integer, String> producerFactory()
        {
            return new DefaultKafkaProducerFactory<>(producerConfigs());
        }

        @Bean
        public Map<String, Object> producerConfigs()
        {
            Map<String, Object> props = new HashMap<>();
            props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
            props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            return props;
        }

        @Bean
        public KafkaTemplate<Integer, String> kafkaTemplate()
        {
            return new KafkaTemplate<>(producerFactory());
        }
    }

KafkaProducer

@Component
public class KafkaProducer
{

    @Autowired
    private KafkaTemplate kafkaTemplate;

    public void sendMessage(String message, final String topicName)
    {

        ListenableFuture<SendResult<String, String>> future = kafkaTemplate.send(topicName, message);

        future.addCallback(new ListenableFutureCallback<SendResult<String, String>>()
        {

            @Override
            public void onSuccess(SendResult<String, String> result)
            {
                System.out.println("Sent message=[" + message
                        + "] with offset=[" + result.getRecordMetadata().offset() + "]");
            }

            @Override
            public void onFailure(Throwable ex)
            {
                System.out.println("Unable to send message=["
                        + message + "] due to : " + ex.getMessage());
            }
        });
    }
}

KafkaConsumer

@Component
public class KafkaConsumer
{

    @KafkaListener(id = "basic", topics = "test-1", clientIdPrefix = "test-prefix-id", autoStartup = "true", concurrency = "3")
    public void multipleTransactionNotification(@Payload final String message)
    {
        try
        {
            Thread.sleep(1000);
            System.out.println(message);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

TestApplication

@SpringBootApplication
public class TestApplication
{    
    public static void main(String[] args)
    {
        SpringApplication.run(TestApplication.class, args);
    }

   @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx, KafkaProducer producer, NIPKafkaConsumer consumer) {
        return args -> {

            for (int i = 0; i < 100_000; i++)
            {
                producer.sendMessage("New Message-" + i, "test-1");
            }

        };
    }    

}

1 个答案:

答案 0 :(得分:0)

问题1

Spring Kafka执行KafkaConsumer.poll()来获取记录,然后为每个记录调用您的侦听器。

因此,如果该批次包含100条记录,将需要100秒才能使用您的实际KafkaListener进行整个批次。

问题2

消费者提交偏移量以知道从何处恢复。

默认行为是每5秒自动提交一次偏移。我的猜测是,您要在消费者支付其偏移量之前就杀死它,这就是为什么它从头开始重新启动的原因。

请参阅enable.auto.commitauto.commit.interval.ms Kafka用户配置。

您还可以手动提交偏移量。

另请参见auto.offset.reset来定义您的消费者在没有初始偏移量时应该怎么做。