[Kafka] Cola de mensajes Conceptos básicos de Kafka

Introducción a Message Queue Server

  Cola de mensajes, a menudo abreviada como MQ. Entendida literalmente, una cola de mensajes es una cola utilizada para almacenar mensajes. Por ejemplo, una cola en Java:

// 1. 创建一个保存字符串的队列
Queue<String> stringQueue = new LinkedList<String>();
// 2. 往消息队列中放入消息
stringQueue.offer("message");
// 3. 从消息队列中取出消息并打印
System.out.println(stringQueue.poll());

  El código anterior crea una cola, primero agrega un mensaje a la cola y luego saca un mensaje de la cola. Esto muestra que la cola se puede utilizar para acceder a los mensajes. Simplemente podemos entender que la cola de mensajes es para almacenar los datos que deben transmitirse en la cola.
  El middleware de cola de mensajes es un software (componentes) que se utiliza para almacenar mensajes. Hay muchas colas de mensajes, como: Kafka, RabbitMQ, ActiveMQ, RocketMQ, ZeroMQ, etc.

Escenarios de aplicación de Message Queue Server

procesamiento asíncrono

  Por ejemplo, en un sitio web de comercio electrónico, cuando un nuevo usuario se registra, la información del usuario debe guardarse en la base de datos y debe enviarse al usuario una notificación de registro adicional por correo electrónico y un código de registro por SMS.
  Sin embargo, debido a que el envío de correos electrónicos y el envío de SMS de registro deben conectarse a un servidor externo, debe esperar un período de tiempo.En este momento, puede usar las colas de mensajes para el procesamiento asincrónico, a fin de lograr una respuesta rápida.
inserte la descripción de la imagen aquí

Desacoplamiento del sistema

inserte la descripción de la imagen aquí

recorte de flujo

inserte la descripción de la imagen aquí

procesamiento de registros

  Los sitios web de comercio electrónico a gran escala (Taobao, JD.com, Gome, Suning...), las aplicaciones (Douyin, Meituan, Didi, etc.) necesitan analizar el comportamiento del usuario y descubrir las preferencias y actividades del usuario en función del comportamiento de acceso del usuario. En la página se recopila una gran cantidad de información de acceso de los usuarios.
inserte la descripción de la imagen aquí

Dos modos de cola de mensajes

modo de igual a igual

  El remitente del mensaje produce un mensaje y lo envía a la cola de mensajes, y luego el receptor del mensaje lo saca de la cola de mensajes y lo consume. Una vez consumido el mensaje, no hay más almacenamiento en la cola de mensajes, por lo que es imposible que el receptor del mensaje consuma el mensaje que ya se ha consumido.

Características:

  • Cada mensaje tiene un solo receptor (Consumidor) (es decir, una vez consumido, el mensaje ya no está en la cola de mensajes)
  • No hay dependencia entre el remitente y el receptor.Después de que el remitente envía un mensaje, no importa si el receptor se está ejecutando o no, no afectará el próximo envío del mensaje del remitente;
  • Después de recibir con éxito el mensaje, el receptor debe responder a la cola con éxito, para que la cola de mensajes pueda eliminar el mensaje recibido actualmente;

inserte la descripción de la imagen aquí

modelo de publicación-suscripción

Características:

  • Cada mensaje puede tener múltiples suscriptores;
  • Hay una dependencia de tiempo entre editores y suscriptores. Para un suscriptor de un tema (Topic), debe crear un suscriptor antes de poder consumir el mensaje del editor.
  • Para consumir mensajes, los suscriptores deben suscribirse al tema del rol con anticipación y seguir ejecutándose en línea;

inserte la descripción de la imagen aquí

Introducción a Kafka y escenarios de aplicación

Apache Kafka es una plataforma de transmisión distribuida. Una plataforma de transmisión distribuida debe contener tres capacidades clave:

  • Publique y suscríbase a flujos de datos de transmisión, similar a las colas de mensajes o los sistemas de mensajería empresarial
  • Almacenar flujos de datos de manera persistente tolerante a fallas
  • flujo de datos de proceso

Apache Kafka se usa normalmente en dos tipos de programas:

  • Cree canalizaciones de datos en tiempo real para obtener datos de manera confiable entre sistemas o aplicaciones

  • Cree aplicaciones de transmisión en tiempo real que transformen o reaccionen a flujos de datos

Producers: Puede haber muchas aplicaciones que coloquen datos de mensajes en el clúster de Kafka.
Consumers: Puede haber muchas aplicaciones que extraen datos de mensajes del clúster de Kafka.
Connectors: El conector de Kafka puede importar datos de la base de datos a Kafka y también puede exportar datos de Kafka a la base de datos.
Stream Processors: El procesador de flujo puede extraer datos de Kafka o escribir datos en Kafka.

inserte la descripción de la imagen aquí

Ventajas de Kafka sobre otros MQ

característica ActiveMQ ConejoMQ Kafka RocketMQ
Comunidad/Empresa apache Licencia pública de Mozilla apache apache/ali
Madurez Maduro Maduro Maduro mas maduro
patrón productor-consumidor apoyo apoyo apoyo apoyo
publicar-suscribir apoyo apoyo apoyo apoyo
SOLICITUD-RESPUESTA apoyo apoyo - apoyo
Completitud de la API alto alto alto bajo (configuración estática)
soporte multilingüe Apoyar la prioridad de JAVA independiente del idioma Soporte, prioridad JAVA apoyo
Rendimiento independiente 10,000 (peor) nivel 10,000 nivel 100,000 Nivel 100,000 (más alto)
retraso del mensaje - nivel de microsegundos Milisegundo -
disponibilidad alto (maestro-esclavo) alto (maestro-esclavo) muy alto (distribuido) alto
mensaje perdido - Bajo teóricamente no se pierde -
repetición de mensaje - controlable En teoría habrá repetición. -
asuntos apoyo no apoyo apoyo apoyo
integridad de la documentación alto alto alto medio
Proporciona un inicio rápido tener tener tener ninguno
Dificultad del primer despliegue - Bajo medio alto

Estructura de directorios de Kafka

La versión de Kafka utilizada es la 2.4.1.

nombre del directorio ilustrar
papelera Todos los scripts de ejecución de Kafka están aquí. Por ejemplo: inicie el servidor Kafka, cree un tema, productor, programa de consumo, etc.
configuración Todos los archivos de configuración de Kafka
liberaciones Todos los paquetes JAR necesarios para ejecutar Kafka
registros Todos los archivos de registro de Kafka, si hay algún problema con Kafka, debe verificar la información de excepción en este directorio
sitio-docs Archivo de ayuda del sitio web de Kafka

Cree un clúster de Kafka

La versión de Kafka utilizada es la 2.4.1, que se lanzó el 12 de marzo de 2020.

Nota : el número de versión de Kafka es: kafka_2.12-2.4.1, porque Kafka se desarrolla principalmente con el lenguaje scala, y 2.12 es el número de versión de scala.

Crear y descomprimir:

sudo mkdir export
cd /export
sudo mkdir server
sudo mkdir software
sudo chmod 777 software/
sudo chmod 777 server/
cd /export/software/
tar -xvzf kafka_2.12-2.4.1.tgz -C ../server/

Modificar server.properties:

# 创建Kafka数据的位置
mkdir /export/server/kafka_2.12-2.4.1/data
vim /export/server/kafka_2.12-2.4.1/config/server.properties
# 指定broker的id
broker.id=0
# 指定Kafka数据的位置
log.dirs=/export/server/kafka_2.12-2.4.1/data
# 配置zk的三个节点
zookeeper.connect=10.211.55.8:2181,10.211.55.9:2181,10.211.55.7:2181

Repita los pasos anteriores para los otros dos servidores, solo modifique el broker.id para que sea diferente.
Configure la variable de entorno KAFKA_HOME:

sudo su
vim /etc/profile
export KAFKA_HOME=/export/server/kafka_2.12-2.4.1
export PATH=:$PATH:${KAFKA_HOME}
#源文件无下面这条需手动添加
export PATH

每个节点加载环境变量
source /etc/profile

Inicie el servidor:

# 启动ZooKeeper
# 启动Kafka,需要在kafka根目录下启动
cd /export/server/kafka_2.12-2.4.1

nohup bin/kafka-server-start.sh config/server.properties &
# 测试Kafka集群是否启动成功
bin/kafka-topics.sh --bootstrap-server 10.211.55.8:9092 --list
# 无报错,打印为空

Escriba el script de inicio/apagado de Kafka con un solo clic

Para facilitar el inicio y apagado de Kafka con un solo clic en el futuro, se puede escribir un script de shell para que funcione, y Kafka se puede iniciar o cerrar rápidamente siempre que el script se ejecute una vez.

Prepare el archivo de configuración del esclavo para guardar kafka en qué nodos comenzar:

# 创建 /export/onekey 目录
sudo mkdir onekey

cd /export/onekey
sudo su
#新建slave文件
touch slave

#slave中写入以下内容
10.211.55.8
10.211.55.9
10.211.55.7

Escriba el script start-kafka.sh:

vim start-kafka.sh

cat /export/onekey/slave | while read line
do
{
    
    
 echo $line
 ssh $line "source /etc/profile;export JMX_PORT=9988;nohup ${KAFKA_HOME}/bin/kafka-server-start.sh ${KAFKA_HOME}/config/server.properties >/dev/nul* 2>&1 & "
 wait
}&
done

Escriba el script stop-kafka.sh:

vim stop-kafka.sh

cat /export/onekey/slave | while read line
do
{
    
    
 echo $line
 ssh $line "source /etc/profile;jps |grep Kafka |cut -d' ' -f1 |xargs kill -s 9"
 wait
}&
done

Configure los permisos de ejecución para start-kafka.sh y stop-kafka.sh:

chmod u+x start-kafka.sh
chmod u+x stop-kafka.sh

# 执行一键启动、一键关闭,注:执行shell脚本需实现服务器间ssh免密登录
./start-kafka.sh
./stop-kafka.sh

# 当查看日志发生Error connecting to node ubuntu2:9092错误时需在三台服务器上配置如下命令,以ubuntu2为例,另外两台同样的规则配置
# sudo vim /etc/hosts
# 10.211.55.8 ubuntu1
# 10.211.55.7 ubuntu3

Operaciones básicas de Kafka

crear tema

Crear un tema (tema). Todos los mensajes en Kafka se almacenan en temas. Para producir mensajes para Kafka, primero debe tener un tema determinado.
inserte la descripción de la imagen aquí

# 创建名为test的主题
bin/kafka-topics.sh --create --bootstrap-server 10.211.55.8:9092 --topic test
# 查看目前Kafka中的主题
bin/kafka-topics.sh --list --bootstrap-server 10.211.55.8:9092
# 成功打印出 test

Producir mensajes a Kafka

Utilice el programa de prueba integrado de Kafka para generar algunos mensajes sobre el tema de prueba de Kafka.

bin/kafka-console-producer.sh --broker-list 10.211.55.8:9092 --topic test
# “>”表示等待输入

Consumir mensajes de Kafka

Abre otra ventana:

# 使用消费 test 主题中的消息。
bin/kafka-console-consumer.sh --bootstrap-server 10.211.55.8:9092 --topic test --from-beginning

# 实现了生产者发送消息,消费者接受消息

Uso de Kafka Tools para trabajar con Kafka

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Conéctese a la herramienta Kafka con seguridad

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Operación de programación Java Kafka

Importe las dependencias de Maven Kafka pom.xml:

<repositories><!-- 代码库 -->
    <repository>
        <id>central</id>
        <url>http://maven.aliyun.com/nexus/content/groups/public//</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
            <updatePolicy>always</updatePolicy>
            <checksumPolicy>fail</checksumPolicy>
 				</snapshots>
    </repository>
</repositories>

<dependencies>
    <!-- kafka客户端工具 -->
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>2.4.1</version>
    </dependency>

    <!-- 工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
    </dependency>

    <!-- SLF桥接LOG4J日志 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.6</version>
    </dependency>

    <!-- SLOG4J日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.16</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <source>1.8</source>
        				<target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

log4j.properties: (poner en la carpeta de recursos)

log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern=%5p - %m%n

Sincronice los mensajes de producción con Kafka:

  • Cree una configuración de Propiedades para conectarse a Kafka
Properties props = new Properties();
//这个配置是 Kafka 生产者和消费者必须要指定的一个配置项,它用于指定 Kafka 集群中的一个或多个 broker 地址,生产者和消费者将使用这些地址与 Kafka 集群建立连接。
props.put("bootstrap.servers", "192.168.88.100:9092");
//这行代码将 acks 配置设置为 all。acks 配置用于指定消息确认的级别。在此配置下,生产者将等待所有副本都成功写入后才会认为消息发送成功。这种配置级别可以确保数据不会丢失,但可能会影响性能。
props.put("acks", "all");
//这行代码将键(key)序列化器的类名设置为 org.apache.kafka.common.serialization.StringSerializer。键和值都需要被序列化以便于在网络上传输。这里使用的是一个字符串序列化器,它将字符串序列化为字节数组以便于发送到 Kafka 集群。
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//这行代码将值(value)序列化器的类名设置为 org.apache.kafka.common.serialization.StringSerializer。这里同样使用的是一个字符串序列化器,它将字符串序列化为字节数组以便于发送到 Kafka 集群。
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
  • Crear un objeto productor KafkaProducer
  • Llamar a enviar para enviar de 1 a 100 mensajes a la prueba de tema especificada y obtener el valor de retorno Futuro, que encapsula el valor de retorno
  • Luego llame a un método Future.get() para esperar la respuesta
  • productor de cierre

Enviar un mensaje usando una espera sincrónica

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * Kafka的生产者程序,会将消息创建出来,并发送到Kafka集群中
 * 1. 创建用于连接Kafka的Properties配置
 * 2. 创建一个生产者对象KafkaProducer
 * 3. 调用send发送1-100消息到指定Topic test,并获取返回值Future,该对象封装了返回值
 * 4. 再调用一个Future.get()方法等待响应
 * 5. 关闭生产者
 */
public class KafkaProducerTest {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 创建用于连接Kafka的Properties配置
        Properties props = new Properties();
        props.put("bootstrap.servers", "172.xx.xx.1x8:9092");
        props.put("acks", "all");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        props.put("security.protocol", "SASL_PLAINTEXT");
        props.put("sasl.mechanism", "PLAIN");

        props.put("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"xxxx\" password=\"xxxx\";");

        // 实现生产者的幂等性
        props.put("enable.idempotence",true);

        // 创建一个生产者对象KafkaProducer
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);

        // 发送1-100的消息到指定的topic中
        for (int i = 0; i < 100; ++i) {
    
    
            // 一、使用同步等待的方式发送消息
            // 构建一条消息,直接new ProducerRecord
            //"test":这个参数是指定 Kafka 主题(topic)的名称,表示这条记录将被发送到哪个主题中。
            // null:这个参数表示记录的键(key)。在 Kafka 中,每条消息都可以有一个键值对,键是一个可选参数,如果没有设置,则为 null。
            //i + "":这个参数表示记录的值(value)。这里的 i 是一个整数,通过将它转换为字符串来设置记录的值。这个值将被序列化为字节数组并被发送到 Kafka 集群。
            ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test", null, i + "");
            Future<RecordMetadata> future = kafkaProducer.send(producerRecord);
            
            // 调用Future的get方法等待响应
            future.get();
            System.out.println("第" + i + "条消息写入成功!");
        }

        // 关闭生产者
        kafkaProducer.close();
    }
}

Usar métodos asincrónicamente con funciones de devolución de llamada para generar mensajes

Si desea saber si el mensaje del productor es exitoso o después de producir correctamente el mensaje a Kafka, realice otras acciones. En este punto, es conveniente utilizar una función con devolución de llamada para enviar un mensaje.

  • Cuando ocurre una excepción al enviar un mensaje, la información de la excepción se puede imprimir a tiempo
  • Cuando el mensaje se envíe correctamente, imprima el nombre del tema de Kafka, la identificación de la partición, el desplazamiento
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * Kafka的生产者程序,会将消息创建出来,并发送到Kafka集群中
 * 1. 创建用于连接Kafka的Properties配置
 * 2. 创建一个生产者对象KafkaProducer
 * 3. 调用send发送1-100消息到指定Topic test,并获取返回值Future,该对象封装了返回值
 * 4. 再调用一个Future.get()方法等待响应
 * 5. 关闭生产者
 */
public class KafkaProducerTest {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 创建用于连接Kafka的Properties配置
        Properties props = new Properties();
        props.put("bootstrap.servers", "172.16.4.158:9092");
        props.put("acks", "all");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        props.put("security.protocol", "SASL_PLAINTEXT");
        props.put("sasl.mechanism", "PLAIN");

        props.put("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin\";");

        //实现生产者的幂等性
        props.put("enable.idempotence",true);

        // 创建一个生产者对象KafkaProducer
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);

        // 发送1-100的消息到指定的topic中
        for (int i = 0; i < 100; ++i) {
    
    
            // 二、使用异步回调的方式发送消息
            ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test", null, i + "");
            //使用匿名内部类实现Callback接口,该接口中表示Kafka服务器响应给客户端,会自动调用onCompletion方法
            //metadata:消息的元数据(属于哪个topic、属于哪个partition、对应的offset是什么)
            //exception:这个对象Kafka生产消息封装了出现的异常,如果为null,表示发送成功,如果不为null,表示出现异常。
            kafkaProducer.send(producerRecord, new Callback() {
    
    
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
    
    
                    // 1. 判断发送消息是否成功
                    if(exception == null) {
    
    
                        // 发送成功
                        // 主题
                        String topic = metadata.topic();
                        // 分区id
                        int partition = metadata.partition();
                        // 偏移量
                        long offset = metadata.offset();
                        System.out.println("topic:" + topic + " 分区id:" + partition + " 偏移量:" + offset);
                    }
                    else {
    
    
                        // 发送出现错误
                        System.out.println("生产消息出现异常!");
                        // 打印异常消息
                        System.out.println(exception.getMessage());
                        // 打印调用栈
                        System.out.println(exception.getStackTrace());
                    }
                }
            });
        }

        // 4.关闭生产者
        kafkaProducer.close();
    }
}

Consumir mensajes de temas de Kafka

Desde el tema de prueba, consuma todos los mensajes e imprima el desplazamiento, la clave y el valor registrados.

  • Crear configuración de consumidor de Kafka
Properties props = new Properties();
//这一行将属性"bootstrap.servers"的值设置为"node1.itcast.cn:9092"。这是Kafka生产者和消费者所需的Kafka集群地址和端口号。
props.setProperty("bootstrap.servers", "node1.itcast.cn:9092");
//这一行将属性"group.id"的值设置为"test"。这是消费者组的唯一标识符。所有属于同一组的消费者将共享一个消费者组ID。
props.setProperty("group.id", "test");
//这一行将属性"enable.auto.commit"的值设置为"true"。这表示消费者是否应该自动提交偏移量。
props.setProperty("enable.auto.commit", "true");
//这一行将属性"auto.commit.interval.ms"的值设置为"1000"。这是消费者自动提交偏移量的时间间隔,以毫秒为单位。
props.setProperty("auto.commit.interval.ms", "1000");
//这两行将属性"key.deserializer"和"value.deserializer"的值都设置为"org.apache.kafka.common.serialization.StringDeserializer"。这是用于反序列化Kafka消息的Java类的名称。在这种情况下,消息的键和值都是字符串类型,因此使用了StringDeserializer类来反序列化它们。
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
  • Crear un consumidor de Kafka
  • Suscríbete a temas para consumir
  • Use un ciclo while para extraer continuamente mensajes del tema de Kafka
  • Imprimirá el desplazamiento, la clave y el valor del registro (registro)
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;

/**
 * 消费者程序
 * 1.创建Kafka消费者配置
 * 2.创建Kafka消费者
 * 3.订阅要消费的主题
 * 4.使用一个while循环,不断从Kafka的topic中拉取消息
 * 5.将将记录(record)的offset、key、value都打印出来
 */
public class KafkaConsumerTest {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        // 创建Kafka消费者配置
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "172.16.4.158:9092");
        props.setProperty("group.id", "test");
        props.setProperty("enable.auto.commit", "true");
        props.setProperty("auto.commit.interval.ms", "1000");
        props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        props.put("security.protocol", "SASL_PLAINTEXT");
        props.put("sasl.mechanism", "PLAIN");

        props.put("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"xxxx\" password=\"xxxx\";");

        // 创建Kafka消费者
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props);

        // 订阅要消费的主题
        // 指定消费者从哪个topic中拉取数据
        kafkaConsumer.subscribe(Arrays.asList("test"));

        // 使用一个while循环,不断从Kafka的topic中拉取消息
        while(true) {
    
    
            // Kafka的消费者一次拉取一批的数据
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
            // 将将记录(record)的offset、key、value都打印出来
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
    
    
                // 主题
                String topic = consumerRecord.topic();
                // offset:这条消息处于Kafka分区中的哪个位置
                long offset = consumerRecord.offset();
                // key和value
                String key = consumerRecord.key();
                String value = consumerRecord.value();

                System.out.println("topic: " + topic + " offset:" + offset + " key:" + key + " value:" + value);
            }
        }
    }
}

Conceptos clave de Kafka

corredor

  Un clúster de Kafka generalmente consta de varios intermediarios para lograr el equilibrio de carga y la tolerancia a fallas. Los corredores no tienen estado (Sateless) y mantienen el estado del clúster a través de ZooKeeper. Un bróker de Kafka puede manejar cientos de miles de lecturas y escrituras por segundo, y cada bróker puede manejar mensajes TB sin afectar el rendimiento.
inserte la descripción de la imagen aquí

cuidador del zoológico

  ZK se usa para administrar y coordinar intermediarios y almacena metadatos de Kafka (por ejemplo: cuántos temas, particiones, consumidores hay). El servicio ZK se utiliza principalmente para notificar a productores y consumidores que un nuevo intermediario se ha unido al clúster de Kafka o un intermediario que ha fallado en el clúster de Kafka.

Nota : Kafka está tratando de encontrar gradualmente una manera de eliminar ZooKeeper, y el costo de mantener dos conjuntos de clústeres es alto. La comunidad propuso que KIP-500 reemplace la dependencia de ZooKeeper. "Kafka on Kafka": Kafka gestiona sus propios metadatos.

Kafka Tool puede ver la configuración de ZooKeeper
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

productor

El productor es responsable de enviar datos al tema del corredor

consumidor

Los consumidores son responsables de extraer datos del tema del corredor y procesarlos ellos mismos.

grupo de consumidores

  El grupo de consumidores es un mecanismo de consumidor escalable y tolerante a fallas proporcionado por Kafka. Un grupo de consumidores puede contener varios consumidores. Un grupo de consumidores tiene una ID única (ID de grupo). Los consumidores del grupo consumen todos los datos de partición del tema juntos.
inserte la descripción de la imagen aquí

Tema

  Un tema es un concepto lógico para que los productores publiquen datos y los consumidores extraigan datos. Los temas en Kafka deben tener identificadores y ser únicos. Puede haber cualquier número de temas en Kafka, y no hay límite en el número. Los mensajes en el tema están estructurados, generalmente un tema contiene cierto tipo de mensajes. Una vez que un productor envía mensajes a un tema, esos mensajes no se pueden actualizar (cambiar)
inserte la descripción de la imagen aquí

Particiones

  En un clúster de Kafka, los temas se dividen en particiones. En Kafka, los mensajes del mismo tema se pueden asignar a diferentes particiones y las reglas de asignación específicas dependen del particionador.

  Kafka proporciona una implementación de partición predeterminada, llamada DefaultPartitioner, que aplica un hash a la clave del mensaje (si existe) y luego determina a qué partición debe asignarse el mensaje en función del valor hash. Si el mensaje no tiene clave, el mensaje se asigna a diferentes particiones en forma de sondeo.

  Además del particionador predeterminado, los usuarios también pueden personalizar la implementación del particionador para satisfacer diferentes necesidades. La implementación del particionador personalizado debe implementar la interfaz del particionador proporcionada por Kafka y especificar el particionador que se usará en la configuración del productor.
  Ya sea que utilice el particionador predeterminado o un particionador personalizado, se deben seguir las siguientes reglas:

  • Para una misma clave, siempre se asigna a la misma partición.
  • Para los mensajes sin claves, los mensajes deben asignarse a diferentes particiones de forma aleatoria o por sondeo.

  Cabe señalar que los cambios en el número de particiones también pueden hacer que los mensajes se asignen a diferentes particiones. Por ejemplo, cuando cambia el número de particiones de un tema, los mensajes escritos previamente pueden redistribuirse a diferentes particiones. Por lo tanto, los cambios en el número de particiones deben manejarse con cuidado en el código del productor para evitar la pérdida o duplicación de datos.
inserte la descripción de la imagen aquí

Réplicas

  Las réplicas garantizan que los datos sigan estando disponibles en caso de que falle el servidor. En Kafka, el número de réplicas generalmente se diseña para que sea >1.
inserte la descripción de la imagen aquí

compensar (compensar)

  offset registra el número de secuencia del siguiente mensaje que se enviará al Consumidor. De forma predeterminada, Kafka almacena las compensaciones en ZooKeeper. En una partición, los mensajes se almacenan de forma secuencial y cada consumo en la partición tiene un id creciente. Este es el desplazamiento compensado. Los desplazamientos solo son significativos dentro de las particiones. Entre particiones, el desplazamiento no tiene sentido.

grupo de consumidores

  Kafka es compatible con varios consumidores que consumen datos de un tema al mismo tiempo. Inicie dos consumidores juntos para consumir los datos del tema de prueba.

Modifique el programa del productor para que el productor siga produciendo números del 1 al 100 cada 3 segundos:

// 发送1-100数字到Kafka的test主题中
while(true) {
    
    
    for (int i = 1; i <= 100; ++i) {
    
    
        // 注意:send方法是一个异步方法,它会将要发送的数据放入到一个buffer中,然后立即返回
        // 这样可以让消息发送变得更高效
        producer.send(new ProducerRecord<>("test", i + ""));
    }
    Thread.sleep(3000);
}

Ejecute dos consumidores al mismo tiempo:
inserte la descripción de la imagen aquí
se puede encontrar que solo un programa de consumidor puede extraer el mensaje. Si desea que dos consumidores consuman mensajes al mismo tiempo, debe agregar una partición al tema de prueba.

# 设置 test topic为2个分区
bin/kafka-topics.sh --zookeeper 10.211.55.8:2181 -alter --partitions 2 --topic test

Vuelva a ejecutar los programas productor y dos consumidores, y podrá ver que ambos consumidores pueden consumir datos de Kafka Topic.

Idempotencia del productor Kafka

  Tome http como ejemplo, una o más solicitudes, la respuesta es consistente (excepto por el tiempo de espera de la red y otros problemas), en otras palabras, el impacto de realizar múltiples operaciones es el mismo que realizar una operación. Si un sistema no es idempotente, si un usuario envía un formulario repetidamente, puede causar efectos adversos. Por ejemplo, si el usuario hace clic en el botón Enviar pedido varias veces en el navegador, se generarán varios pedidos idénticos en segundo plano.

  Idempotencia del productor de Kafka: cuando un productor produce un mensaje, si se produce un reintento, se puede enviar un mensaje varias veces. Si Kafka no es idempotente, puede guardar varios mensajes idénticos en las noticias de la partición.
inserte la descripción de la imagen aquí

//配置幂等性
props.put("enable.idempotence",true);

Principio de idempotencia

Para darse cuenta de la idempotencia de los productores, Kafka introduce los conceptos de ID de productor (PID) y número de secuencia.

  • PID: A cada Productor se le asigna un PID único cuando se inicializa, y este PID es transparente para el usuario.
  • Número de secuencia: el mensaje enviado a la partición de tema especificada para cada productor (correspondiente a PID) corresponde a un Número de secuencia que se incrementa desde 0.
    inserte la descripción de la imagen aquí

Transacciones de Kafka

  Las transacciones de Kafka son una nueva característica introducida en Kafka 0.11.0.0 en 2017.
  Similar a las transacciones de base de datos. La transacción de Kafka se refiere a la operación de los productores que producen mensajes y los consumidores que envían compensaciones en una operación atómica, ya sea con éxito o sin éxito. Especialmente cuando coexisten productores y consumidores, la protección de las transacciones es particularmente importante. ( consumer-transform-producermodo)
inserte la descripción de la imagen aquí

API de operación de transacción:

Los siguientes cinco métodos relacionados con transacciones se definen en la interfaz de Producer:

  • initTransactions (transacción de inicialización): para utilizar las transacciones de Kafka, primero se debe realizar la operación de inicialización.
  • beginTransaction (comenzar transacción): inicia una transacción de Kafka.
  • sendOffsetsToTransaction (commit offset): Envía las compensaciones correspondientes a las particiones a la transacción en lotes para facilitar el envío de bloques posteriores.
  • commitTransaction (confirmar transacción): Confirmar la transacción.
  • abortTransaction (abandonar la transacción): cancelar la transacción.

Programación de transacciones Kafka

Configuración de atributos relacionados con transacciones

Productor:

// 配置事务的id,开启了事务会默认开启幂等性
props.put("transactional.id", "first-transactional");

consumidor:

// 消费者需要设置隔离级别
props.put("isolation.level","read_committed");
// 关闭自动提交,开启事务的,不能开启offset自动提交,假设每秒提交一次,offset不受事务控制
props.put("enable.auto.commit", "false");

Caso de programación de transacciones Kafka

Requisitos: hay algunos datos de usuario en el tema [ods_user] de Kafka, el formato de datos es el siguiente:

姓名,性别,出生日期
张三,1,1980-10-09
李四,0,1985-11-01

Es necesario escribir un programa para convertir el género del usuario en masculino y femenino (1-masculino, 0-femenino) y escribir los datos en el tema [dwd_user] después de la conversión.
Es necesario utilizar la garantía de transacción o consumir datos y escribir datos en el tema al mismo tiempo y enviar la compensación. o todos fallan.

Inicie el programa de la consola del productor para simular datos:

# 创建名为ods_user和dwd_user的主题
bin/kafka-topics.sh --create --bootstrap-server 10.211.55.8:9092 --topic ods_user
bin/kafka-topics.sh --create --bootstrap-server 10.211.55.8:9092 --topic dwd_user

# 窗口一:生产数据到 ods_user
bin/kafka-console-producer.sh --broker-list 10.211.55.8:9092 --topic ods_user

# 窗口二:从 dwd_user 消费数据
bin/kafka-console-consumer.sh --bootstrap-server 10.211.55.8:9092 --topic dwd_user --from-beginning  --isolation-level read_committed

Crear código de consumidor:
método createConsumer, que devuelve un consumidor para suscribirse al tema [ods_user]. Nota: debe configurar el nivel de aislamiento de transacciones y desactivar la confirmación automática.

	//创建消费者
    public static Consumer<String, String> createConsumer() {
    
    
        // 1. 创建Kafka消费者配置
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "10.211.55.8:9092");
        props.setProperty("group.id", "ods_user");
        props.put("isolation.level","read_committed");
        props.setProperty("enable.auto.commit", "false");
        props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

        // 2. 创建Kafka消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        // 3. 订阅要消费的主题
        consumer.subscribe(Arrays.asList("ods_user"));
        
        return consumer;
}

Escriba el código para crear el productor:
el método createProducer devuelve un objeto productor. Nota: Es necesario configurar el id de la transacción. Si la transacción está habilitada, la idempotencia se habilitará de forma predeterminada.

Nota : si usa transacciones, no use envío asíncrono

	//创建生产者
	public static Producer<String, String> createProduceer() {
    
    
        // 1. 创建生产者配置
        Properties props = new Properties();
        props.put("bootstrap.servers", "10.211.55.8:9092");
        props.put("transactional.id", "dwd_user");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        // 2. 创建生产者
        Producer<String, String> producer = new KafkaProducer<>(props);
        return producer;
    }

Escriba código para consumir y producir datos:

pasos :

  • Llame al método implementado anteriormente para crear objetos de consumidor y productor.
  • El productor llama a initTransactions para inicializar la transacción.
  • Escriba un ciclo while, extraiga continuamente datos en el ciclo while, procéselos y luego escríbalos en el tema especificado.

En el ciclo while:

  • El productor inicia la transacción.
  • El consumidor extrae mensajes
  • Recorra los mensajes obtenidos y realice el preprocesamiento (convierta 1 en masculino y 0 en femenino)
  • Producir mensajes al tema [dwd_user]
  • Confirmar la compensación en la transacción
  • confirmar transacción
  • Detecte la excepción y cancele la transacción si ocurre una excepción
    public static void main(String[] args) {
    
    
    	// 调用之前实现的方法,创建消费者、生产者对象
        Consumer<String, String> consumer = createConsumer();
        Producer<String, String> producer = createProducer();
        // 初始化事务
        producer.initTransactions();

		// 在while死循环中不断拉取数据,进行处理后,再写入到指定的topic
        while (true) {
    
    
            try {
    
    
                // 1. 开启事务
                producer.beginTransaction();
                // 2. 定义Map结构,用于保存分区对应的offset
                Map<TopicPartition, OffsetAndMetadata> offsetCommits = new HashMap<>();
                // 2. 拉取消息
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(2));

                for (ConsumerRecord<String, String> record : records) {
    
    
                    // 3. 保存偏移量
                    //将当前消息所属分区的偏移量保存到HashMap中,并且将偏移量加1,以便下次从此偏移量开始消费消息。
                    offsetCommits.put(new TopicPartition(record.topic(), record.partition()), new OffsetAndMetadata(record.offset() + 1));

                    // 4. 进行转换处理
                    String[] fields = record.value().split(",");
                    fields[1] = fields[1].equalsIgnoreCase("1") ? "男" : "女";
                    String message = fields[0] + "," + fields[1] + "," + fields[2];
                    
                    // 5. 生产消息到dwd_user
                    producer.send(new ProducerRecord<>("dwd_user", message));
                }
                
                // 6. 提交偏移量到事务,
                producer.sendOffsetsToTransaction(offsetCommits, "ods_user");
                // 7. 提交事务
                producer.commitTransaction();
            }
            catch (Exception e) {
    
    
                // 8. 放弃事务
                producer.abortTransaction();
            }
        }
    }

  Confirmar el desplazamiento del mensaje consumido a la transacción del productor es para garantizar que el desplazamiento del mensaje consumido se haya registrado y guardado en la transacción antes de que el productor envíe el mensaje al nuevo tema.
  Si no se envía el desplazamiento, es posible que los mensajes consumidos se consuman repetidamente la próxima vez que se inicie el consumidor.
  Por lo tanto, es muy importante comprometer la compensación con la transacción del productor para garantizar que el consumidor pueda continuar consumiendo correctamente desde la última posición de parada cuando comience la próxima vez.

Prueba:
convertido y consumido con éxito:
inserte la descripción de la imagen aquí
transacción de prueba anómala simulada:

// 3. 保存偏移量
offsetCommits.put(new TopicPartition(record.topic(), record.partition()),new OffsetAndMetadata(record.offset() + 1));

// 4. 进行转换处理
String[] fields = record.value().split(",");
fields[1] = fields[1].equalsIgnoreCase("1") ? "男":"女";
String message = fields[0] + "," + fields[1] + "," + fields[2];

// 模拟异常
int i = 1/0;

// 5. 生产消息到dwd_user
producer.send(new ProducerRecord<>("dwd_user", message));

Inicie el programa una vez, se lanza una excepción. Vuelva a iniciar el programa o lance una excepción. Hasta que manejemos esa excepción.

inserte la descripción de la imagen aquí
El mensaje se puede consumir, pero si ocurre una excepción en el medio, la compensación no se enviará y la transacción no se enviará a menos que el consumo y la producción del mensaje sean exitosos.

Supongo que te gusta

Origin blog.csdn.net/qq_44033208/article/details/131917719
Recomendado
Clasificación