我在卡夫卡很新。
为了对集群进行压力测试并建立操作经验,我创建了两个简单的Java应用程序:一个重复将消息发布到主题(整数序列),另一个应用程序加载整个主题(所有记录)。并验证序列是否完整。期望不会因群集上的操作(重新启动节点,替换节点,主题分区重新配置等)而丢失消息。
主题“序列”具有两个分区,复制因子3。该群集由3个虚拟节点组成(出于测试目的,因此它们在同一台计算机上运行)。该主题已配置为保留所有消息(retention.ms
设置为-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()
?
当话题变大时,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);
}
}
}
答案 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,这是我的答案:
关于制作人:感谢您的评论。我承认从书中拿到了例子,并在不考虑性能的情况下直接对其进行了修改。
关于消费者:如上所述,订阅是尝试的第一种方法,不幸的是产生了我的问题中所述的相同结果:来自单个分区的结果,并且很少来自同一调用中的两个分区。我也很想了解这种明显随机行为的原因!
有关消费者的更多信息:在每个周期我都会回到主题的开头,因为目的是不断地验证序列没有中断(因此不会丢失任何消息)。在每个周期我都会加载所有消息并检查它们。
因为基于主题订阅的单个调用产生了明显的随机行为(不确定何时返回主题的全部内容);在检查它们之前,我必须从每个分区中读取并手动加入记录列表-这不是我最初想要做的!
我的方法错了吗?