web-dev-qa-db-fra.com

Comment utiliser plusieurs consommateurs dans Kafka?

Je suis un nouvel étudiant étudiant Kafka et j'ai rencontré des problèmes fondamentaux avec la compréhension de plusieurs consommateurs avec lesquels les articles, les documentations, etc. n'ont pas été trop utiles jusqu'à présent.

Une chose que j'ai essayé de faire est d'écrire mon propre producteur et consommateur de haut niveau Kafka producteur et consommateur et de les exécuter simultanément, en publiant 100 messages simples sur un sujet et en demandant à mon consommateur de les récupérer. J'ai réussi à faire cela avec succès, mais lorsque j'essaie de présenter un deuxième consommateur à consommer à partir du même sujet que les messages qui viennent d'être publiés, il ne reçoit aucun message.

Je croyais comprendre que pour chaque sujet, il pouvait y avoir des consommateurs de groupes de consommateurs distincts et chacun de ces groupes de consommateurs recevrait une copie complète des messages produits sur un sujet donné. Est-ce correct? Sinon, quelle serait la bonne façon pour moi de configurer plusieurs consommateurs? Voici la classe de consommateurs que j'ai écrite jusqu'à présent:

public class AlternateConsumer extends Thread {
    private final KafkaConsumer<Integer, String> consumer;
    private final String topic;
    private final Boolean isAsync = false;

    public AlternateConsumer(String topic, String consumerGroup) {
        Properties properties = new Properties();
        properties.put("bootstrap.servers", "localhost:9092");
        properties.put("group.id", consumerGroup);
        properties.put("partition.assignment.strategy", "roundrobin");
        properties.put("enable.auto.commit", "true");
        properties.put("auto.commit.interval.ms", "1000");
        properties.put("session.timeout.ms", "30000");
        properties.put("key.deserializer", "org.Apache.kafka.common.serialization.IntegerDeserializer");
        properties.put("value.deserializer", "org.Apache.kafka.common.serialization.StringDeserializer");
        consumer = new KafkaConsumer<Integer, String>(properties);
        consumer.subscribe(topic);
        this.topic = topic;
    }


    public void run() {
        while (true) {
            ConsumerRecords<Integer, String> records = consumer.poll(0);
            for (ConsumerRecord<Integer, String> record : records) {
                System.out.println("We received message: " + record.value() + " from topic: " + record.topic());
            }
        }

    }
}

De plus, j'ai remarqué qu'à l'origine, je testais la consommation ci-dessus pour un sujet "test" avec une seule partition. Lorsque j'ai ajouté un autre consommateur à un groupe de consommateurs existant, par exemple "testGroup", cela a déclenché un Kafka rééquilibrage qui a ralenti considérablement la latence de ma consommation, en quelques secondes. Je pensais qu'il s'agissait d'un problème de rééquilibrage, car je n'avais qu'une seule partition, mais lorsque j'ai créé un nouveau sujet "plusieurs partitions" avec, disons, 6 partitions, des problèmes similaires se sont posés lorsque l'ajout de consommateurs au même groupe de consommateurs a provoqué des problèmes de latence. et les gens me disent que je devrais utiliser un consommateur multi-thread - quelqu'un peut-il faire la lumière là-dessus?

29
Jeff Gong

Je pense que votre problème réside dans la propriété auto.offset.reset. Lorsqu'un nouveau consommateur lit à partir d'une partition et qu'il n'y a aucun décalage validé antérieur, la propriété auto.offset.reset est utilisée pour décider quel doit être le décalage de départ. Si vous le définissez sur "le plus grand" (par défaut), vous commencez à lire le dernier (dernier) message. Si vous le définissez sur "le plus petit", vous obtenez le premier message disponible.

Alors ajoutez:

properties.put("auto.offset.reset", "smallest");

et essayez à nouveau.

* modifier *

"plus petit" et "plus grand" ont été dépréciés il y a quelque temps. Vous devez utiliser "au plus tôt" ou "au plus tard" maintenant. Toutes les questions, vérifiez le docs

20
Chris Gerken

Si vous souhaitez que plusieurs consommateurs consomment les mêmes messages (comme une diffusion), vous pouvez les générer avec un groupe de consommateurs différent et également définir auto.offset.reset sur le plus petit dans la configuration du consommateur. Si vous souhaitez que plusieurs consommateurs finissent de consommer en parallèle (divisez le travail entre eux), vous devez créer un nombre de partitions> = nombre de consommateurs. Une partition ne peut être consommée que par au plus un processus consommateur. Mais un consommateur peut consommer plusieurs partitions.

7
user1119541

Dans la documentation ici il est dit: "si vous fournissez plus de threads qu'il n'y a de partitions sur le sujet, certains threads ne verront jamais de message". Pouvez-vous ajouter des partitions à votre sujet? Le nombre de threads de mon groupe de consommateurs est égal au nombre de partitions de mon sujet et chaque thread reçoit des messages.

Voici ma configuration de sujet:

buffalo-macbook10:kafka_2.10-0.8.2.1 aakture$ bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic recent-wins
Topic:recent-wins   PartitionCount:3    ReplicationFactor:1 Configs:
Topic: recent-wins  Partition: 0    Leader: 0   Replicas: 0 Isr: 0
Topic: recent-wins  Partition: 1    Leader: 0   Replicas: 0 Isr: 0
Topic: recent-wins  Partition: 2    Leader: 0   Replicas: 0 Isr: 0

Et mon consommateur:

package com.cie.dispatcher.services;

import com.cie.dispatcher.model.WinNotification;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import io.dropwizard.lifecycle.Managed;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import Java.util.HashMap;
import Java.util.List;
import Java.util.Map;
import Java.util.Properties;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
import Java.util.concurrent.TimeUnit;

/**
 * This will create three threads, assign them to a "group" and listen for  notifications on a topic.
 * Current setup is to have three partitions in Kafka, so we need a thread per partition (as recommended by
 * the kafka folks). This implements the dropwizard Managed interface, so it can be started and stopped by the
 * lifecycle manager in dropwizard.
 * <p/>
 * Created by aakture on 6/15/15.
 */
public class KafkaTopicListener implements Managed {
private static final Logger LOG = LoggerFactory.getLogger(KafkaTopicListener.class);
private final ConsumerConnector consumer;
private final String topic;
private ExecutorService executor;
private int threadCount;
private WinNotificationWorkflow winNotificationWorkflow;
private ObjectMapper objectMapper;

@Inject
public KafkaTopicListener(String a_zookeeper,
                          String a_groupId, String a_topic,
                          int threadCount,
                          WinNotificationWorkflow winNotificationWorkflow,
                          ObjectMapper objectMapper) {
    consumer = kafka.consumer.Consumer.createJavaConsumerConnector(
            createConsumerConfig(a_zookeeper, a_groupId));
    this.topic = a_topic;
    this.threadCount = threadCount;
    this.winNotificationWorkflow = winNotificationWorkflow;
    this.objectMapper = objectMapper;
}

/**
 * Creates the config for a connection
 *
 * @param zookeeper the Host:port for zookeeper, "localhost:2181" for example.
 * @param groupId   the group id to use for the consumer group. Can be anything, it's used by kafka to organize the consumer threads.
 * @return the config props
 */
private static ConsumerConfig createConsumerConfig(String zookeeper, String groupId) {
    Properties props = new Properties();
    props.put("zookeeper.connect", zookeeper);
    props.put("group.id", groupId);
    props.put("zookeeper.session.timeout.ms", "400");
    props.put("zookeeper.sync.time.ms", "200");
    props.put("auto.commit.interval.ms", "1000");

    return new ConsumerConfig(props);
}

public void stop() {
    if (consumer != null) consumer.shutdown();
    if (executor != null) executor.shutdown();
    try {
        if (!executor.awaitTermination(5000, TimeUnit.MILLISECONDS)) {
            LOG.info("Timed out waiting for consumer threads to shut down, exiting uncleanly");
        }
    } catch (InterruptedException e) {
        LOG.info("Interrupted during shutdown, exiting uncleanly");
    }
    LOG.info("{} shutdown successfully", this.getClass().getName());
}
/**
 * Starts the listener
 */
public void start() {
    Map<String, Integer> topicCountMap = new HashMap<>();
    topicCountMap.put(topic, new Integer(threadCount));
    Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
    List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);
    executor = Executors.newFixedThreadPool(threadCount);
    int threadNumber = 0;
    for (final KafkaStream stream : streams) {
        executor.submit(new ListenerThread(stream, threadNumber));
        threadNumber++;
    }
}

private class ListenerThread implements Runnable {
    private KafkaStream m_stream;
    private int m_threadNumber;

    public ListenerThread(KafkaStream a_stream, int a_threadNumber) {
        m_threadNumber = a_threadNumber;
        m_stream = a_stream;
    }

    public void run() {
        try {
            String message = null;
            LOG.info("started listener thread: {}", m_threadNumber);
            ConsumerIterator<byte[], byte[]> it = m_stream.iterator();
            while (it.hasNext()) {
                try {
                    message = new String(it.next().message());
                    LOG.info("receive message by " + m_threadNumber + " : " + message);
                    WinNotification winNotification = objectMapper.readValue(message, WinNotification.class);
                    winNotificationWorkflow.process(winNotification);
                } catch (Exception ex) {
                    LOG.error("error processing queue for message: " + message, ex);
                }
            }
            LOG.info("Shutting down listener thread: " + m_threadNumber);
        } catch (Exception ex) {
            LOG.error("error:", ex);
        }
    }
  }
}
5
Alper Akture