Kafka:每个poll()调用一次只能消耗一个主题吗?

时间:2019-04-22 17:08:35

标签: apache-kafka

我有一个来自多个Kafka主题的Kafka使用者。我希望能够通过每100条消息进行1次I / O调用来批量写入目的地,但是要进行批量处理,所有消息都必须来自同一主题。

如果我有多个主题(比如说5),并且当Consumer.poll或consumer.consume发生时,例如每次民意调查我收到100条消息,是否有办法确保这些主题均来自同一主题,以便可以将这些消息批量写入同一目标?这样下一个.poll调用会得到下一个主题?

3 个答案:

答案 0 :(得分:1)

无法按主题轮询-您已订阅主题列表,并且每个主题可能具有多个分区。给定的轮询将获取ConsumerRecords对象,该对象是ConsumerRecord的容器。 ConsumerRecord代表一个KV对,它属于您已订阅的主题之一的分区之一。

Kafka尝试将TopicPartition分配给基于分配器组成单个组的消费者。如果您只有一个使用者,它将要求所有主题的所有分区。那么,没有什么可以阻止您在应用程序代码中进行分组

例如

private void consume() {
    List<String> topics = List.of("topic1", "topic2", "topic3", "topic4", "topic5");
    kafkaConsumer.subscribe(topics);

    while (true) {
        ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(1000);

        topics.forEach(s -> {
            List<ConsumerRecord<String, String>> recordsPerTopicPartition = new ArrayList<>();
            consumerRecords.records(s).forEach(recordsPerTopicPartition::add);
            doWhatever(recordsPerTopicPartition);
        });
    }
}

private void doWhatever(List<ConsumerRecord<String, String>> consumerRecords) {
    //process
}

答案 1 :(得分:0)

另一种处理主题的方法如下:每个ProducerRecord都有一个方法topic(),该方法返回该记录的主题名称。然后,您可以按主题分组,并通过主题对和该主题的记录集合来做任何您想做的事情。

但是,如果您要独立处理主题,我强烈建议对每个单独的主题使用单独的KafkaConsumer。

答案 2 :(得分:0)

在按每个主题轮询消息主题时订阅多个主题的一种解决方案是使用暂停/继续方法。

这里是一个示例:

        List<String> subscription = List.of("topic-a", "topic-b");
        consumer.subscribe(suubscription);

        final Map<String, List<TopicPartition>> partitionsPerTopic = 
                consumer.assignment()
                .stream()
                .collect(Collectors.groupingBy(TopicPartition::topic, Collectors.toList()));

        int next = 0;
        consumer.pause(consumer.assignment());
        // Starting consumption
        while (!closed.get()) {
            // Resuming consumption for next topic
            final String topic = subscription.get(next);
            consumer.resume(partitionsPerTopic.get(topic));

            consumer.poll(Duration.ofMillis(500)).forEach( records -> {
            ...
            });   

            // Pausing consumption for current topic
            consumer.pause(partitionsPerTopic.get(topic));
            next = (next + 1) % subscription.size();
       }

但是,此解决方案可能效率不高,因为使用者仍然可以为先前的可提取分区保留缓冲的消息。这些消息将被清空,并在第二次迭代时再次获取。

换句话说,该解决方案可以增加消费者和代理之间的网络往返。