卡夫卡:KafkaConsumer无法提取所有记录

时间:2019-07-05 08:34:21

标签: apache-kafka kafka-consumer-api

我在卡夫卡很新。

为了对集群进行压力测试并建立操作经验,我创建了两个简单的Java应用程序:一个重复将消息发布到主题(整数序列),另一个应用程序加载整个主题(所有记录)。并验证序列是否完整。期望不会因群集上的操作(重新启动节点,替换节点,主题分区重新配置等)而丢失消息。

主题“序列”具有两个分区,复制因子3。该群集由3个虚拟节点组成(出于测试目的,因此它们在同一台计算机上运行)。该主题已配置为保留所有消息(retention.ms设置为-1

我目前有两个问题,难以解决:

  1. 如果我使用bin/kafka-console-consumer.sh --bootstrap-server kafka-test-server:9090,kafka-test-server:9091,kafka-test-server:9092 --topic sequence --from-beginning,则会在控制台上看到所有消息(即使未按预期排序)。另一方面,如果我使用编写的使用者应用程序,则在每个周期都会加载不同的结果:https://i.stack.imgur.com/tMK10.png-在控制台输出中,除数的第一行是对{{1}的调用},因此有时仅从两个分区中提取记录。为什么以及为什么Java应用程序的行为不像records.partitions()

  2. 当话题变大时,bin/kafka-console-consumer.sh仍然可以显示所有消息,而应用程序只能加载大约18'000条消息。我试着和 各种用户端配置,没有任何进展。再次,问题是为什么会有区别?

在此先感谢您的提示!

这里供参考。讨论了两个应用程序:

bin/kafka-console-consumer.sh
package ch.demo.toys;

import java.io.FileInputStream;
import java.util.Properties;
import java.util.concurrent.Future;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

public class SequenceProducer {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();

        properties.load(new FileInputStream("toy.properties"));

        properties.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("acks", "1");
        properties.put("retries", "3");
        properties.put("compression.type", "snappy");
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1);

        for (Integer sequence_i = 0; true; sequence_i++) {
            try(Producer<Integer, String> producer = new KafkaProducer<>(properties)) {

                ProducerRecord<Integer, String> record = new ProducerRecord<>("sequence", sequence_i, "Sequence number: " + String.valueOf(sequence_i));

                Future<RecordMetadata> sendFuture = producer.send(record, (metadata, exception) -> {

                    System.out.println("Adding " + record.key() + " to partition " + metadata.partition());

                    if (exception != null) {
                        exception.printStackTrace();
                    }
                });
            }

            Thread.sleep(200);
        }
    }
}

2 个答案:

答案 0 :(得分:0)

您应在客户逻辑中进行一些更改。

制作人:

您正在为要发送的每条记录创建一个新的生产者。就性能而言,这是可怕的,因为每个生产者都在sendign记录之前首先进行引导。同样,由于每个生产者都发送一条记录,因此不会进行批处理。最后,对单个记录的压缩也不存在。

您应该首先创建一个生产者并使用它发送所有记录,即将创建移出循环,诸如:

try (Producer<Integer, String> producer = new KafkaProducer<>(properties)) {
    for (int sequence_i = 18310; true; sequence_i++) {

        ProducerRecord<Integer, String> record = new ProducerRecord<>("sequence", sequence_i, "Sequence number: " + String.valueOf(sequence_i));

        producer.send(record, (metadata, exception) -> {

            System.out.println("Adding " + record.key() + " to partition " + metadata.partition());

            if (exception != null) {
                exception.printStackTrace();
            }
        });
        Thread.sleep(200L);
    }
}

消费者:

在for循环的每次迭代中,您都可以更改分配并搜索回到分区的开头,因此,充其量您每次都会重新使用相同的消息!

首先,您可能应该使用subscribe() API(例如kafka-console-consumer.sh),这样就不必摆弄分区了。例如:

try (Consumer<Integer, String> consumer = new KafkaConsumer<>(properties)) {

    consumer.subscribe(Collections.singletonList("topic"));

    while (true) {
        List<Integer> sequence = new ArrayList<>();

        ConsumerRecords<Integer, String> records = consumer.poll(Duration.ofSeconds(1L));
        records.forEach(record -> {
            sequence.add(record.key());
        });

        System.out.println(sequence.size());
        checkConsistency(sequence);

        Thread.sleep(2500L);
    }
}

答案 1 :(得分:0)

谢谢Mickael-Maison,这是我的答案:

关于制作人:感谢您的评论。我承认从书中拿到了例子,并在不考虑性能的情况下直接对其进行了修改。

关于消费者:如上所述,订阅是尝试的第一种方法,不幸的是产生了我的问题中所述的相同结果:来自单个分区的结果,并且很少来自同一调用中的两个分区。我也很想了解这种明显随机行为的原因!

有关消费者的更多信息:在每个周期我都会回到主题的开头,因为目的是不断地验证序列没有中断(因此不会丢失任何消息)。在每个周期我都会加载所有消息并检查它们。

因为基于主题订阅的单个调用产生了明显的随机行为(不确定何时返回主题的全部内容);在检查它们之前,我必须从每个分区中读取并手动加入记录列表-这不是我最初想要做的!

我的方法错了吗?