如何从一开始就使用Kafka Consumer API读取数据?

时间:2015-02-17 12:10:19

标签: apache-kafka kafka-consumer-api

每次运行消费者jar时,任何人都可以告诉我如何使用Kafka Consumer API读取消息。

11 个答案:

答案 0 :(得分:36)

这适用于0.9.x消费者。基本上,在创建使用者时,需要使用属性ConsumerConfig.GROUP_ID_CONFIG为此使用者分配使用者组ID。每次启动使用者时都会随机生成使用者组ID properties.put(ConsumerConfig.GROUP_ID_CONFIG, UUID.randomUUID().toString());(属性是java.util.Properties的一个实例,您将传递给构造函数new KafkaConsumer(properties))。

随机生成客户端意味着新的使用者组在kafka中没有与之关联的任何偏移量。因此,在此之后我们要做的是为此方案设置策略。正如auto.offset.reset属性的文档所示:

  

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

     
      
  • 最早:自动将偏移重置为最早的偏移量
  •   
  • 最新:自动将偏移重置为最新偏移量
  •   
  • none:如果未找到先前的偏移量或消费者的组
  • ,则向使用者抛出异常   
  • 其他任何事情:向消费者抛出异常。
  •   

因此,从上面列出的选项中我们需要选择earliest政策,以便新的消费者群体每次都从头开始。

您在java中的代码看起来像这样:

properties.put(ConsumerConfig.GROUP_ID_CONFIG, UUID.randomUUID().toString());
properties.put(ConsumerConfig.CLIENT_ID_CONFIG, "your_client_id");
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
consumer = new KafkaConsumer(properties);

现在唯一需要弄清楚的是,当多个消费者属于同一个消费者群体但是分布式如何生成随机ID并在这些实例之间分配它们以便它们都属于同一个消费者组。

希望它有所帮助!

答案 1 :(得分:14)

执行此操作的一个选项是每次启动时都有一个唯一的组ID,这意味着Kafka会从头开始向您发送主题中的消息。在为KafkaConsumer设置属性时执行以下操作:

properties.put(ConsumerConfig.GROUP_ID_CONFIG, UUID.randomUUID().toString());

另一种选择是使用consumer.seekToBeginning(consumer.assignment())但这不起作用,除非Kafka首先通过让消费者调用poll方法从您的消费者获得心跳。所以请致电poll(),然后执行seekToBeginning(),如果您想从一开始就想要所有记录,请再次致电poll()。这是一个小小的hackey,但这似乎是0.9版本中最可靠的方法。

// At this point, there is no heartbeat from consumer so seekToBeinning() wont work
// So call poll()
consumer.poll(0);
// Now there is heartbeat and consumer is "alive"
consumer.seekToBeginning(consumer.assignment());
// Now consume
ConsumerRecords<String, String> records = consumer.poll(0);

答案 2 :(得分:4)

1)https://stackoverflow.com/a/17084401/3821653

2)http://mail-archives.apache.org/mod_mbox/kafka-users/201403.mbox/%3CCAOG_4QYz2ynH45a8kXb8qw7xw4vDRRwNqMn5j9ERFxJ8RfKGCg@mail.gmail.com%3E

要重置使用者组,您可以删除Zookeeper组ID

 import kafka.utils.ZkUtils;
 ZkUtils.maybeDeletePath(<zkhost:zkport>, </consumers/group.id>);`

答案 3 :(得分:4)

一种可能的解决方案是在订阅一个或多个主题时使用 ConsumerRebalanceListener 的实现。 ConsumerRebalanceListener包含在从使用者分配或删除新分区时的回调方法。以下代码示例说明了这一点:

public class SkillsConsumer {

private String topic;

private KafkaConsumer<String, String> consumer;

private static final int POLL_TIMEOUT = 5000;

public SkillsConsumer(String topic) {
    this.topic = topic;
    Properties properties = ConsumerUtil.getConsumerProperties();
    properties.put("group.id", "consumer-skills");
    this.consumer = new KafkaConsumer<>(properties);
    this.consumer.subscribe(Collections.singletonList(this.topic),
            new PartitionOffsetAssignerListener(this.consumer));
    }
}

public class PartitionOffsetAssignerListener implements ConsumerRebalanceListener {

private KafkaConsumer consumer;

public PartitionOffsetAssignerListener(KafkaConsumer kafkaConsumer) {
    this.consumer = kafkaConsumer;
}

@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {

}

@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
    //reading all partitions from the beginning
    for(TopicPartition partition : partitions)
        consumer.seekToBeginning(partition);
}

}

现在,只要将分区分配给使用者,就会从头开始读取每个分区。

答案 4 :(得分:2)

在创建props.put("auto.offset.reset", "smallest");

时使用高级消费者集ConsumerConfig

答案 5 :(得分:1)

如果您正在使用java consumer api,更具体地说是org.apache.kafka.clients.consumer.Consumer,您可以尝试seek *方法。

consumer.seekToBeginning(consumer.assignment())

这里,consumer.assignment()返回分配给给定使用者的所有分区,seekToBeginning将从给定分区集合的最早偏移开始。

答案 6 :(得分:1)

所以对我来说,有效的方法是上述建议的结合。关键的更改是要包含

props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

,并且每次都有一个随机生成的GROUP ID。但这本身对我不起作用。由于某种原因,我第一次对消费者进行调查时,从未获得任何记录。我必须破解它才能使其正常工作-

consumer.poll(0); // without this the below statement never got any records
final ConsumerRecords<Long, String> consumerRecords = consumer.poll(Duration.ofMillis(100));

我是KAFKA的新手,不知道为什么会这样,但是对于仍在尝试使它起作用的其他人,希望这会有所帮助。

答案 7 :(得分:1)

这是我从头开始读取消息的代码(使用 Java 11)

try (var consumer = new KafkaConsumer<String, String>(config)) {
     
        consumer.subscribe(Set.of(topic), new ConsumerRebalanceListener() {
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
            }

            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                consumer.seekToBeginning(partitions);
            }
        });
        // polling messages
}

您可以在此处查看完整的代码示例:

https://gist.github.com/vndung/4c9527b3aeafec5d3245c7a3b921f8b1

答案 8 :(得分:0)

props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

如果您只是避免保存任何偏移量,那么使用者将始终在开始时重置。

答案 9 :(得分:0)

另一种选择是使您的使用者代码保持简单,并使用Kafka随附的命令行工具kafka-consumer-groups从外部引导偏移量管理。

每次启动消费者之前,您都要致电

bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
 --execute --reset-offsets \
 --group myConsumerGroup \
 --topic myTopic \
 --to-earliest

根据您的要求,您可以使用该工具重置主题每个分区的偏移量。帮助功能或documentation解释选项:

--reset-offsets also has following scenarios to choose from (atleast one scenario must be selected):

--to-datetime <String: datetime> : Reset offsets to offsets from datetime. Format: 'YYYY-MM-DDTHH:mm:SS.sss'
--to-earliest : Reset offsets to earliest offset.
--to-latest : Reset offsets to latest offset.
--shift-by <Long: number-of-offsets> : Reset offsets shifting current offset by 'n', where 'n' can be positive or negative.
--from-file : Reset offsets to values defined in CSV file.
--to-current : Resets offsets to current offset.
--by-duration <String: duration> : Reset offsets to offset by duration from current timestamp. Format: 'PnDTnHnMnS'
--to-offset : Reset offsets to a specific offset.

答案 10 :(得分:0)

始终从偏移量0读取而不每次都创建新的groupId。

    // ... Assuming the props have been set properly.
    // ... enable.auto.commit and auto.offset.reset as default

    KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    consumer.subscribe(Collections.singletonList(topic));
    consumer.poll(0);  // without this, the assignment will be empty. 
    consumer.assignment().forEach(t -> {
        System.out.printf("Set %s to offset 0%n", t.toString());
        consumer.seek(t, 0);
    });
    while (true) {
     // ... consumer polls messages as usual.
    }