web-dev-qa-db-fra.com

Comment utiliser le consommateur multi-thread dans kafka 0.9.0?

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;
        }
    }

code flink de multi-thread

Qu'est-ce qui ne va pas?

8
Acceml

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. 

9
Lan

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;
    }
}
3
Steve

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.

2
demonodojo