Explication détaillée du fonctionnement de l'API consommateur Kafka Consumer

1. Préparation

  • Créez un projet maven sur l'EDI, ajoutez des dépendances au fichier pom
<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>1.1.1</version>
</dependency>
  • Démarrer le cluster zookeeper
bin/zkServer.sh start
  • Démarrez le cluster kafka
bin/kafka-server-start.sh -daemon config/server.properties
  • Le cluster Kafka ouvre un producteur
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic mydata 

Deuxièmement, créez un consommateur ordinaire (soumettez automatiquement le décalage)

La fiabilité de la consommation des données des consommateurs est facile à garantir, car les données sont persistantes dans Kafka, il n'y a donc pas lieu de s'inquiéter de la perte de données.

Étant donné que le consommateur peut subir des pannes telles que des pannes de courant et des temps d'arrêt pendant le processus de consommation, après la récupération du consommateur, il doit continuer à consommer à partir de l'emplacement avant la panne, de sorte que le consommateur doit enregistrer en temps réel le décalage qu'il consomme afin de pouvoir continuer à consommer après la reprise après panne. Par conséquent, la maintenance de la compensation est un problème que les consommateurs doivent prendre en compte lors de la consommation de données

Dans le code ci-dessous, cette consommation n'enregistrera que le décalage maximum, ce qui équivaut à ne pas ajouter - à partir du début sur la ligne de commande, et les données précédentes ne peuvent pas être consommées

import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启自动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(true));

        /* 自动提交的延时时间 */
        properties.put("auto.commit.interval.ms", "1000");

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));


        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);


            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }
        }
    }
}

Exécuter la capture d'écran:
Insérez la description de l'image ici

Troisièmement, comment re-consommer les données d'un certain sujet (soumettre automatiquement le décalage)

Vous pouvez modifier un groupe de consommateurs et réinitialiser le décalage

/* 指定消费者组 */
properties.put("group.id", "mygroup");

/* 重置消费者的offset */
properties.put("auto.offset.reset","earliest");

Le code source de auto.offset.reset est le suivant: L'une ou l'autre des deux conditions est remplie: lorsque le groupe de consommateurs est modifié ou que les données enregistrées deviennent invalides (lorsque l'offset n'est pas valide), cela prendra effet et réinitialisera l'offset. Il existe deux types effectifs: la valeur par défaut est le dernier décalage, qui peut être modifié au plus tôt, le décalage le plus ancien.

    public static final String AUTO_OFFSET_RESET_CONFIG = "auto.offset.reset";
    public static final String AUTO_OFFSET_RESET_DOC = "What to do when there is no 
    initial offset in Kafka or if the current offset does not exist any more on the 
    server (e.g. because that data has been deleted): <ul><li>earliest: automatically 
    reset the offset to the earliest offset<li>latest: automatically reset the offset to 
    the latest offset</li><li>none: throw exception to the consumer if no previous offset 
    is found for the consumer's group</li><li>anything else: throw exception to the 
    consumer.</li></ul>";

Le code détaillé est le suivant:

import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;

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

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启自动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(true));

        /* 自动提交的延时时间 */
        properties.put("auto.commit.interval.ms", "1000");

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup1");

        /* 重置消费者的offset */
        properties.put("auto.offset.reset","earliest");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));


        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }
        }
    }
}

Quatre, soumettez manuellement le décalage

  • Bien que la soumission automatique de l'offset soit très concise, il est difficile pour les développeurs de saisir le moment de la soumission de l'offset ( temps de retard ) car il est soumis en fonction du temps . Par conséquent, Kafka fournit également une API pour soumettre manuellement le décalage.

  • Il existe deux méthodes pour soumettre manuellement un offset: commitSync (soumission synchrone) et commitAsync (soumission asynchrone).

    La similitude entre les deux est qu'ils soumettront le décalage le plus élevé d'un lot de données de sondage;

    La différence est que commitSync bloque le thread actuel jusqu'à ce que la soumission réussisse, et échouera automatiquement et réessayera (en raison de facteurs incontrôlables, il y aura également des échecs de soumission); commitAsync n'a pas de mécanisme de nouvelle tentative d'échec, donc la soumission peut échouer.

  • Qu'il soit soumis de manière synchrone ou asynchrone, il peut entraîner une fuite de données ou une consommation répétée. Soumettre le décalage en premier, puis le consommer peut entraîner une fuite de données; et soumettre le décalage après l'avoir consommé peut entraîner une consommation répétée de données

Soumission synchrone

import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.*;

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

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启手动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(false));

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));

        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }

            /* 同步提交,当前线程会阻塞直到 offset 提交成功 */
            consumer.commitSync();

        }
    }
}

Soumission asynchrone:

import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

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

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启手动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(false));

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 订阅的主题,可同时订阅多个 */
        consumer.subscribe(Collections.singletonList("bigdata"));


        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());
            }

            /* 异步提交 */
            consumer.commitAsync(new OffsetCommitCallback() {
    
    
                @Override
                public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
    
    
                    if (exception != null){
    
    
                        System.err.println("提交失败" + offsets);
                    }else {
    
    
                        System.err.println("提交成功" + offsets);
                    }
                }
            });
        }
    }
}

Cinq, décalage de stockage personnalisé

  • Avant la version 0.9 de Kafka, l'offset était stocké dans zookeeper, et après la version 0.9, l'offset était stocké par défaut dans une rubrique intégrée dans Kafka. De plus, Kafka peut également choisir de personnaliser le décalage de stockage. La maintenance de l'offset est assez lourde, car le rééquilibrage du consommateur doit être pris en compte .

  • Lorsqu'un nouveau consommateur rejoint un groupe de consommateurs, qu'un consommateur existant lance un groupe de consommateurs ou que la partition d'un sujet abonné change, la redistribution de la partition est déclenchée. Le processus de redistribution est appelé Rééquilibrage.

  • Une fois le rééquilibrage des consommateurs effectué, la partition de consommation de chaque consommateur changera. Par conséquent, les consommateurs doivent d'abord obtenir la partition à laquelle ils sont réaffectés et localiser la position de décalage récemment soumise par chaque partition pour continuer la consommation.

  • Pour implémenter des décalages de stockage personnalisés, vous devez utiliser la classe ConsumerRebalanceListener . Les méthodes d'envoi et d'obtention de décalages doivent être implémentées par le système de stockage de décalage sélectionné.

import java.util.*;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

public class MyConsumer {
    
    
    private static Map<TopicPartition, Long> currentOffset = new  HashMap<>();

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

        /* 指定连接kafka集群 */
        properties.put("bootstrap.servers", "centos7-1:9092");

        /* 开启手动提交 */
        properties.put("enable.auto.commit", Boolean.valueOf(false));

        /* key的反序列化 */
        properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        /* value的反序列化 */
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        /* 指定消费者组 */
        properties.put("group.id", "mygroup");

        /* 创建消费者对象 */
        KafkaConsumer<String, String> consumer = new KafkaConsumer(properties);

        /* 消费者订阅的主题 */
        consumer.subscribe(Arrays.asList("bigdata"), new ConsumerRebalanceListener() {
    
    

            /* 该方法会在 Rebalance 之前调用 */
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
    
    
                /* 调用自己写的提交所有分区的 offset 方法 */
                commitOffset(currentOffset);
            }

            /* 该方法会在 Rebalance 之后调用 */
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
    
    
                currentOffset.clear();
                /* 遍历 */
                for (TopicPartition partition : partitions) {
    
    
                    /* 定位到最近提交的 offset 位置继续消费 */
                    consumer.seek(partition, getOffset(partition));
                }
            }
        });

        while (true) {
    
    
            /* 获取数据,设置拉取数据延迟时间 */
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            /* 遍历 */
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords)
            {
    
    
                /* 打印消息的键和值 */
                System.out.println(consumerRecord.key() + "==>" + consumerRecord.value());

                currentOffset.put(new TopicPartition(consumerRecord.topic(), consumerRecord.partition()),consumerRecord.offset());
            }

            /* 异步提交 */
            commitOffset(currentOffset);
        }
    }

    /* 获取某分区的最新 offset */
    private static long getOffset(TopicPartition partition) {
    
    
        return 0;
    }

    /* 提交该消费者所有分区的 offset */
    private static void commitOffset(Map<TopicPartition, Long> currentOffset) {
    
    
        /* 自己定义的方法,根据业务逻辑,也可以提交到mysql上,写一个jdbc,创建消费者组、主题、分区字段 */
    }
}

Je suppose que tu aimes

Origine blog.csdn.net/weixin_46122692/article/details/109280040
conseillé
Classement