Explicación detallada del funcionamiento de la API de consumidor de Kafka Consumer

1. Preparación

  • Cree un proyecto maven en el IDE, agregue dependencias al archivo 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>
  • Iniciar grupo de cuidadores del zoológico
bin/zkServer.sh start
  • Inicie el grupo kafka
bin/kafka-server-start.sh -daemon config/server.properties
  • El clúster de Kafka abre un productor
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic mydata 

Dos, crear un consumidor común (enviar compensación automáticamente)

La fiabilidad del consumo de datos del consumidor es fácil de garantizar, porque los datos son persistentes en Kafka, por lo que no hay necesidad de preocuparse por la pérdida de datos.

Dado que el consumidor puede experimentar fallas como cortes de energía y tiempo de inactividad durante el proceso de consumo, después de que el consumidor se recupera, necesita continuar consumiendo desde la ubicación antes de la falla, por lo que el consumidor debe registrar en tiempo real qué compensación consume para que pueda continuar consumiendo después de que se restablezca la falla. Por tanto, el mantenimiento del offset es un problema que los consumidores deben tener en cuenta a la hora de consumir datos

En el siguiente código, este consumo solo registrará el desplazamiento máximo, lo que equivale a no agregar --desde el principio en la línea de comando, y los datos anteriores no se pueden consumir

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

Ejecutar captura de pantalla:
Inserte la descripción de la imagen aquí

En tercer lugar, cómo volver a consumir los datos de un tema determinado (enviar automáticamente el desplazamiento)

Puede cambiar un grupo de consumidores y restablecer la compensación

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

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

El código fuente de auto.offset.reset es el siguiente: satisfaga cualquiera de las dos condiciones: cuando se cambia el grupo de consumidores o los datos guardados se vuelven inválidos (cuando el desplazamiento no es válido), entrará en vigor y se restablecerá el desplazamiento. Hay dos tipos efectivos: el predeterminado es el último desplazamiento, que se puede modificar al más antiguo, el desplazamiento más temprano.

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

El código detallado es el siguiente:

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

Cuatro, envíe manualmente la compensación

  • Aunque la presentación automática de compensación es muy concisa, es difícil para los desarrolladores comprender el momento de la presentación de compensación ( tiempo de demora ) porque se envía en función del tiempo . Por lo tanto, Kafka también proporciona una API para enviar manualmente la compensación.

  • Hay dos métodos para enviar manualmente un desplazamiento: commitSync (envío sincrónico) y commitAsync (envío asincrónico).

    La similitud entre los dos es que enviarán la compensación más alta de un lote de datos de encuesta;

    La diferencia es que commitSync bloquea el hilo actual hasta que el envío sea exitoso, y automáticamente fallará y volverá a intentarlo (debido a factores incontrolables, también habrá fallas de envío); commitAsync no tiene un mecanismo de reintento por falla, por lo que el envío puede fallar.

  • Ya sea que se envíe de forma sincrónica o asincrónica, puede provocar la pérdida de datos o el consumo repetido. Enviar la compensación primero y luego consumirla puede causar una fuga de datos; y enviar la compensación después de consumirla puede causar un consumo repetido de datos

Envío sincrónico

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();

        }
    }
}

Envío asincrónico:

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

Cinco, compensación de almacenamiento personalizada

  • Antes de la versión 0.9 de Kafka, el desplazamiento se almacenaba en zookeeper, y después de la versión 0.9, el desplazamiento se almacenaba en un tema integrado en Kafka de forma predeterminada. Además, Kafka también puede optar por personalizar la compensación de almacenamiento. El mantenimiento de la compensación es bastante engorroso, porque se debe tener en cuenta el reequilibrio del consumidor .

  • Cuando un nuevo consumidor se une a un grupo de consumidores, un consumidor existente lanza un grupo de consumidores o la partición de un tema suscrito cambia, se activará la reasignación de la partición. El proceso de reasignación se llama Rebalance.

  • Una vez que se produce el reequilibrio del consumidor, la partición de consumo de cada consumidor cambiará. Por lo tanto, los consumidores primero deben obtener la partición a la que están reasignados y ubicar la posición de compensación enviada recientemente por cada partición para continuar con el consumo.

  • Para implementar una compensación de almacenamiento personalizada, debe utilizar la clase ConsumerRebalanceListener , en la que los métodos de envío y obtención de la compensación deben implementarse de acuerdo con el sistema de almacenamiento de compensación seleccionado

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,创建消费者组、主题、分区字段 */
    }
}

Supongo que te gusta

Origin blog.csdn.net/weixin_46122692/article/details/109280040
Recomendado
Clasificación