La doc de kafka donne une approche à propos de ce qui suit décrit:
Un consommateur par thread: Une option simple consiste à attribuer à chaque thread sa propre instance de consommateur.
Mon code:
public class KafkaConsumerRunner implements Runnable {
private final AtomicBoolean closed = new AtomicBoolean(false);
private final CloudKafkaConsumer consumer;
private final String topicName;
public KafkaConsumerRunner(CloudKafkaConsumer consumer, String topicName) {
this.consumer = consumer;
this.topicName = topicName;
}
@Override
public void run() {
try {
this.consumer.subscribe(topicName);
ConsumerRecords<String, String> records;
while (!closed.get()) {
synchronized (consumer) {
records = consumer.poll(100);
}
for (ConsumerRecord<String, String> tmp : records) {
System.out.println(tmp.value());
}
}
} catch (WakeupException e) {
// Ignore exception if closing
System.out.println(e);
//if (!closed.get()) throw e;
}
}
// Shutdown hook which can be called from a separate thread
public void shutdown() {
closed.set(true);
consumer.wakeup();
}
public static void main(String[] args) {
CloudKafkaConsumer kafkaConsumer = KafkaConsumerBuilder.builder()
.withBootstrapServers("172.31.1.159:9092")
.withGroupId("test")
.build();
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new KafkaConsumerRunner(kafkaConsumer, "log"));
executorService.execute(new KafkaConsumerRunner(kafkaConsumer, "log.info"));
executorService.shutdown();
}
}
mais cela ne fonctionne pas et jette une exception:
Java.util.ConcurrentModificationException: KafkaConsumer n'est pas sécurisé pour les accès multithreads
De plus, j'ai lu la source de Flink (une plate-forme open source pour le traitement distribué de flux et de données par lots). Flink utilisant un consommateur multi-thread est similaire au mien.
long pollTimeout = Long.parseLong(flinkKafkaConsumer.properties.getProperty(KEY_POLL_TIMEOUT, Long.toString(DEFAULT_POLL_TIMEOUT)));
pollLoop: while (running) {
ConsumerRecords<byte[], byte[]> records;
//noinspection SynchronizeOnNonFinalField
synchronized (flinkKafkaConsumer.consumer) {
try {
records = flinkKafkaConsumer.consumer.poll(pollTimeout);
} catch (WakeupException we) {
if (running) {
throw we;
}
// leave loop
continue;
}
}
Qu'est-ce qui ne va pas?
Kafka consommateur est pas thread-safe . Comme vous l'avez indiqué dans votre question, le document indiquait que
Une option simple consiste à donner à chaque thread sa propre instance de consommateur.
Mais dans votre code, vous avez la même instance de consommateur enveloppée par différentes instances de KafkaConsumerRunner. Ainsi, plusieurs threads accèdent à la même instance de consommateur. La documentation de la kafka indique clairement
Le consommateur Kafka N'EST PAS thread-safe. Toutes les E/S réseau se produisent dans le thread De l'application effectuant l'appel. Il incombe à De s’assurer que l’accès multithread est correctement Synchronisé. Un accès non synchronisé entraînera une exception ConcurrentModificationException.
C'est exactement l'exception que vous avez reçue.
Il lance l'exception sur votre appel pour vous abonner. this.consumer.subscribe(topicName);
Déplacez ce bloc dans un bloc synchronisé comme ceci:
@Override
public void run() {
try {
synchronized (consumer) {
this.consumer.subscribe(topicName);
}
ConsumerRecords<String, String> records;
while (!closed.get()) {
synchronized (consumer) {
records = consumer.poll(100);
}
for (ConsumerRecord<String, String> tmp : records) {
System.out.println(tmp.value());
}
}
} catch (WakeupException e) {
// Ignore exception if closing
System.out.println(e);
//if (!closed.get()) throw e;
}
}
Ce n’est peut-être pas votre cas, mais si vous fusionnez le traitement des données de plusieurs sujets, vous pouvez lire les données de plusieurs sujets avec le même consommateur. Si ce n'est pas le cas, il est préférable de créer des travaux distincts utilisant chaque sujet.