Inicio rápido de kafka Sigue las notas del curso intensivo de una hora de kafka en la estación B

Inicio rápido de kafka Sigue las notas del curso intensivo de una hora de kafka en la estación B

Introducción: Curso introductorio de introducción a kafka de una hora con el video de la estación B (remake en alta definición sin versión sin sentido).
Aquí están las notas del seguimiento. Si tienes tiempo, espero que puedas ver el video original.

1. Origen y desarrollo de Kafka

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

  • LinkedIn
  • apache
  • Confluente

Introducción:

  • Sistema de mensajes distribuidos 0.9.0.x
  • 0.10.0.x Plataforma de procesamiento de flujo distribuido

Ventajas de Kafka

  • Alto rendimiento y buen rendimiento.
  • Buena escalabilidad, admite expansión horizontal en línea
  • Tolerancia a fallas y confiabilidad
  • Estrechamente integrado con la ecología de big data, puede conectar sin problemas hadoop, strom, spark, etc.

versión de lanzamiento

  • Plataforma Confluente
  • Cloudera Kafka
  • Hortonworks Kafka

2. Colas de mensajes comunes

2.1 Especificación JMS API de servicio de mensajes de Java (Servicio de mensajes de Java)

(1) Cola - punto a punto

inserte la descripción de la imagen aquí

(2) Tema - Publicar Suscríbete

inserte la descripción de la imagen aquí

(3).Apache ActiveMQ

2.2 Protocolo de cola de mensajes avanzado de AMQP (Protocolo de cola de mensajes avanzado)

2.2.1 Modelo AMQP

  • cola cola
  • intercambio de buzones
  • unión unión

inserte la descripción de la imagen aquí

Características: Características: Transacciones de soporte, alta consistencia de datos, principalmente utilizado en industrias bancarias y financieras

Middleware típico: RabbitMQ

2.3 Transporte de telemetría de colas de mensajes MQTT

Ampliamente utilizado en IOT (Internet de las cosas)
Diseñado para enviar mensajes cortos entre pequeños dispositivos silenciosos a través de un ancho de banda bajo

3. Temas, particiones, réplicas, intermediarios de mensajes

3.1 Tema temático

Puede entenderse como una tabla en la base de datos, y suele almacenar el mismo tipo de mensajes en el mismo tema. Es solo que las tablas en la base de datos están estructuradas y Topic (tema) son datos semiestructurados. En casos especiales, también se pueden almacenar diferentes tipos de mensajes en el mismo tema.
inserte la descripción de la imagen aquí

3.2 Partición partición

Los temas pueden contener múltiples particiones. Kafka es un sistema de mensajería distribuida y la partición es la base de su distribución. La partición hace que Kafka sea escalable. Kafka divide el tema en varias particiones y las diferentes particiones se almacenan en diferentes servidores, lo que aumenta la escalabilidad de Kafka.
inserte la descripción de la imagen aquí

3.3 Compensación compensada

La partición es un proceso que hace crecer el registro de confirmación de forma lineal. Una vez que un mensaje se almacena en una partición, no se puede cambiar.
Kafka registra la ubicación de cada mensaje por desplazamiento. El mensaje se puede extraer a través del desplazamiento, pero el contenido del mensaje no se puede recuperar ni consultar a través del desplazamiento.
Los desplazamientos son únicos, no repetibles e incrementales dentro de la misma partición.
Los desplazamientos se pueden repetir en las particiones.
inserte la descripción de la imagen aquí

3.4 Grabar registro de mensajes

Los mensajes en Kafka se almacenan en forma de pares clave-valor. Si no se especifica ninguna clave, el valor predeterminado es vacío.
inserte la descripción de la imagen aquí
Cuando no se especifica ninguna clave, Kafka escribe mensajes en diferentes particiones en forma de sondeo.
inserte la descripción de la imagen aquí
Si se especifica la clave del mensaje, el mismo mensaje entrará en la misma partición y se escribirá secuencialmente en la misma partición.
inserte la descripción de la imagen aquí

3.5 Copia de replicación

Si solo hay una copia de la partición, la confiabilidad del mensaje no se puede garantizar una vez que esté caído o dañado y perdido Kafka garantiza la confiabilidad del mensaje a través de la replicación (copia).
Estableciendo replication-factorel número de réplicas.

Ejemplo: aquí el factor de replicación = 3 significa que hay 3 particiones en total.
inserte la descripción de la imagen aquí
A menudo, kafka guardará la partición a través del mecanismo de copia maestro-esclavo.
La partición primaria se denomina líder, la escritura y lectura de mensajes,
y la partición esclava se denomina seguidora, la única responsable de copiar los datos del líder para garantizar la coherencia.

ISR: el conjunto de réplicas que se está sincronizando, en este caso [101,102,103].
Si la copia de un seguidor deja de funcionar y no puede sincronizar los datos normalmente, o si los datos difieren demasiado de los datos del líder, se eliminará del conjunto de ISR y no se agregará al ISR hasta que se restablezca la red o se restablezcan los datos. sincronizado
inserte la descripción de la imagen aquí

3.6 Intermediario de mensajes de intermediario

El intermediario es responsable de las solicitudes de lectura y escritura de datos y escribe datos en el disco. Por lo general, se inicia una instancia de Broker en cada servidor.
Solemos decir que un servidor es un Broker.

inserte la descripción de la imagen aquí
Ejemplo:
el clúster A de Kafka contiene 8 servidores, es decir, 8 agentes, y los temas del clúster tienen 8 particiones, a saber, p0-p7, y el factor de replicación = 3, es decir, cada partición tiene 3 copias. Cada partición tiene 1 líder y 2 seguidores.
Tome la primera partición como ejemplo, p1 es el líder y Broker realizará solicitudes de lectura y escritura para la partición p1, mientras que p0 y p2 son seguidores, y Broker solo realizará operaciones de copia de líder en él.
inserte la descripción de la imagen aquí

3.7 Sección de segmento

3.8 Productor productor

3.9 Consumidor

3.10 Grupo de consumidores Grupo de consumidores

4. Construcción del entorno - local pseudo-distribuido

Para la última versión, vaya directamente al último capítulo, donde se debe instalar zookeeper
Cree un diagrama de implementación de clúster:
inserte la descripción de la imagen aquí

Construir en linux
Adjuntar dirección:
kafka descargar
Código de extracción: nmrs

Después de la descarga, extráigalo al /optdirectorio
inserte la descripción de la imagen aquí

tar -zxvf kafka_2.11-2.4.1.tgz

inserte la descripción de la imagen aquí

Kafka depende de zookeeper.
inserte la descripción de la imagen aquí
Empecemos zookeeper primero (instalado antes)

cd /opt/Zookeeper/apache-zookeeper-3.5.6-bin/bin

Inicie el servidor del cuidador del zoológico

# 启动zookeeper服务端
./zkServer.sh start
# 查看zookeeper服务端状态
./zkServer.sh status

inserte la descripción de la imagen aquí

Simulamos la construcción de un clúster configurando Kafka con 3 puertos diferentes.
Cree un directorio etc para almacenar archivos de configuración

mkdir etc

inserte la descripción de la imagen aquí
copiar archivo de configuración

cp config/zookeeper.properties etc

inserte la descripción de la imagen aquí
Ver el contenido del archivo de configuración

vim zookeeper.properties

inserte la descripción de la imagen aquí
La configuración es la siguiente:
inserte la descripción de la imagen aquí
Crear un archivo de directorio de datos de zookeeper

mkdir zkdata_kafka

copiar archivo de configuración

cp config/server.properties etc/server-0.properties
cp config/server.properties etc/server-1.properties
cp config/server.properties etc/server-2.properties

inserte la descripción de la imagen aquí
Ingrese al directorio etc.

cd etc

inserte la descripción de la imagen aquí

vim server-0.properties

La configuración es la siguiente:
inserte la descripción de la imagen aquí
Crear un directorio de registro
inserte la descripción de la imagen aquí

Configurar la ubicación del registro

log.dirs=/opt/kafka_2.11-2.4.1/logs/kafka-logs-0

inserte la descripción de la imagen aquí
La misma regla modifica server-1

vim server-1.properties

La configuración es la siguiente: asegúrese de que el puerto no entre en conflicto con
inserte la descripción de la imagen aquí
el directorio de registro:

log.dirs=/opt/kafka_2.11-2.4.1/logs/kafka-logs-1

inserte la descripción de la imagen aquí
Las mismas reglas modifican server-2

vim server-2.properties

La configuración es la siguiente: asegúrese de que el puerto no entre en conflicto con
inserte la descripción de la imagen aquí
el directorio de registro:

log.dirs=/opt/kafka_2.11-2.4.1/logs/kafka-logs-2

inserte la descripción de la imagen aquí
Ingrese al directorio bin para comenzar

cd /opt/kafka_2.11-2.4.1/bin

Ejecutar e iniciar zookeeper en el directorio

./zookeeper-server-start.sh  ../etc/zookeeper.properties 

inserte la descripción de la imagen aquí
Inicie kafka
y abra tres nuevas ventanas para ingresar al directorio bin para comenzar

cd /opt/kafka_2.11-2.4.1/bin

Empezar por separado

./kafka-server-start.sh  ../etc/server-0.properties 
./kafka-server-start.sh  ../etc/server-1.properties 
./kafka-server-start.sh  ../etc/server-2.properties 

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

Después de comenzar, puede crear un nuevo tema, creamos una nueva sesión y entramos en el directorio bin.

cd /opt/kafka_2.11-2.4.1/bin

crear tema

./kafka-topics.sh --zookeeper localhost:2181 --create --topic test --partitions 3 --replication-factor 2

Cree un tema de tema, la cantidad de particiones es 3 y la cantidad de copias de partición es 2
inserte la descripción de la imagen aquí
Verifique el estado de partición del tema

./kafka-topics.sh --zookeeper localhost:2181 --describe --topic test

inserte la descripción de la imagen aquí

Crear una nueva ventana de sesión como productor

cd /opt/kafka_2.11-2.4.1/bin
./kafka-console-producer.sh --broker-list localhost:9092,localhost:9093,localhost:9094 --topic test

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

Crear una nueva ventana de sesión como consumidor

cd /opt/kafka_2.11-2.4.1/bin

monitor

./kafka-console-consumer.sh --bootstrap-server localhost:9092,localhost:9093,localhost:9094 --topic test

Nota: Comience al consumidor a escuchar el mensaje primero y luego inicie al productor a enviar el mensaje

Por ejemplo: dejamos que el productor envíe un mensaje
inserte la descripción de la imagen aquí
y el consumidor escucha con éxito
inserte la descripción de la imagen aquí

5. Oyentes y redes internas y externas

Configuración del archivo de configuración
inserte la descripción de la imagen aquí
Configuración de escucha:
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

5.1 Configuración y acceso al listener (intranet)

inserte la descripción de la imagen aquí

5.2 Escenario de configuración del servidor Aliyun (combinación de red externa y red interna)

inserte la descripción de la imagen aquí
Esta vez el
inserte la descripción de la imagen aquí
ejemplo anterior:

listeners=INTERNAL://:9092,EXTERNAL://0.0.0.0:9093
advertised.listeners=INTERNAL://kafka-0:9092,EXTERNAL://公网IP:9093
listener.security.protocol.map=INTERNAL:PLAINTEXT,EXTERNAL:PL AINTEXT
inter.broker.listener.name=INTERNAL

6. Construcción del entorno: despliegue de la ventana acoplable kafka⭐

Vaya a Docker Hub para extraer la imagen
inserte la descripción de la imagen aquí
Visite
kafka-github

El contenido del docker-compose.yml copiado
inserte la descripción de la imagen aquí
es el siguiente:

# kraft通用配置
x-kraft: &common-config
  ALLOW_PLAINTEXT_LISTENER: yes
  KAFKA_ENABLE_KRAFT: yes
  KAFKA_KRAFT_CLUSTER_ID: MTIzNDU2Nzg5MGFiY2RlZg
  KAFKA_CFG_PROCESS_ROLES: broker,controller
  KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
  KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: BROKER:PLAINTEXT,CONTROLLER:PLAINTEXT
  KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka-1:9091,2@kafka-2:9091,3@kafka-3:9091
  KAFKA_CFG_INTER_BROKER_LISTENER_NAME: BROKER

# 镜像通用配置
x-kafka: &kafka
  image: 'bitnami/kafka:3.3.1'
  networks:
    net:

# 自定义网络
networks:
  net:

# project名称
name: kraft
services:
  
  # combined server
  kafka-1:
    <<: *kafka
    container_name: kafka-1
    ports:
      - '9092:9092'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 1
      KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://:9092
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://192.168.2.187:9092 #宿主机IP

  kafka-2:
    <<: *kafka
    container_name: kafka-2
    ports:
      - '9093:9093'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 2
      KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://:9093
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://192.168.2.187:9093 #宿主机IP

  kafka-3:
    <<: *kafka
    container_name: kafka-3
    ports:
      - '9094:9094'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 3
      KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://:9094
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://192.168.2.187:9094 #宿主机IP

  #broker only
  kafka-4:
    <<: *kafka
    container_name: kafka-4
    ports:
      - '9095:9095'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 4
      KAFKA_CFG_PROCESS_ROLES: broker
      KAFKA_CFG_LISTENERS: BROKER://:9095
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://192.168.2.187:9095

inserte la descripción de la imagen aquí

Después de agregar la red interna y externa, el archivo de configuración es el siguiente:

version: "3"

# 通用配置
x-common-config: &common-config
  ALLOW_PLAINTEXT_LISTENER: yes
  KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181
  KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL
  KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT

# kafka镜像通用配置
x-kafka: &kafka
  image: bitnami/kafka:3.2
  networks:
    net:
  depends_on:
    - zookeeper

services:

  zookeeper:
    container_name: zookeeper
    image: bitnami/zookeeper:3.8
    ports:
      - "2181:2181"
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
    networks:
      - net
    volumes:
      - zookeeper_data:/bitnami/zookeeper

  kafka-0:
    container_name: kafka-0
    <<: *kafka
    ports:
      - "9093:9093"
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 0
      KAFKA_CFG_LISTENERS: INTERNAL://:9092,EXTERNAL://0.0.0.0:9093
      KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://kafka-0:9092,EXTERNAL://192.168.131.10:9093 #修改为宿主机IP
    volumes:
      - kafka_0_data:/bitnami/kafka

  kafka-1:
    container_name: kafka-1
    <<: *kafka
    ports:
      - "9094:9094"
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 1
      KAFKA_CFG_LISTENERS: INTERNAL://:9092,EXTERNAL://0.0.0.0:9094
      KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://kafka-1:9092,EXTERNAL://192.168.131.10:9094 #修改为宿主机IP
    volumes:
      - kafka_1_data:/bitnami/kafka

  kafka-2:
    container_name: kafka-2
    <<: *kafka
    ports:
      - "9095:9095"
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 2
      KAFKA_CFG_LISTENERS: INTERNAL://:9092,EXTERNAL://0.0.0.0:9095
      KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://kafka-2:9092,EXTERNAL://192.168.131.10:9095 #修改为宿主机IP
    volumes:
      - kafka_2_data:/bitnami/kafka
      
  nginx:
      container_name: nginx
      hostname: nginx
      image: nginx:1.22.0-alpine
      volumes:
        - ./nginx.conf:/etc/nginx/nginx.conf:ro
      ports:
        - "9093-9095:9093-9095"
      depends_on: 
        - kafka-0
        - kafka-1
        - kafka-2


volumes:
  zookeeper_data:
    driver: local
  kafka_0_data:
    driver: local
  kafka_1_data:
    driver: local
  kafka_2_data:
    driver: local


networks:
  net:

Configurar nginx.conf

stream {
	upstream kafka-0 {
		server kafka-0:9093;
	upstream kafka-1 {
		server kafka-1: 9094;
	upstream kafka-2 {
		server kafka-2:9095;
	}
	server {
		listen 9093;
		proxy_pass kafka-0;
	server {
		listen 9094;
		proxy_pass kafka-1;
	}
	server {
		listen 9095;
		proxy_ pass kafka-2;
	}
}

crear carpeta

mk dir docker-kafka

dentro del directorio ejecutar

docker-compose up -d

Luego verifique si la imagen se creó con éxito'

docker ps -a

crear tema

docker exec -it kafka-0 /opt/bitnami/kafka/bin/kafka-topics.sh \
--create --bootstrap-server kafka-0:9002 \
--topic my-topic \
--partiticons 3 --replication-factor 2

crear consumidor

docker exec -it kafka-0 /opt/bitnami/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka-0:9002 \
--topic my-topic \

crear productor

docker exec -it kafka-0 /opt/bitnami/kafka/bin/kafka-console-producer.sh \
--bootstrap-server kafka-0:9002 \
--topic my-topic \

inserte la descripción de la imagen aquí

7. Modelo de mensaje y secuencia de mensajes

Una partición es la unidad más pequeña de paralelismo.
Un consumidor puede consumir varias particiones.
Una partición puede ser consumida por consumidores en varios grupos de consumidores.
Sin embargo, una partición no puede ser consumida por varios consumidores en el mismo grupo de consumidores al mismo tiempo.

Ejemplo:
Grupo de consumidores A: C1, C2
Grupo de consumidores B: C3, C4, C5, C6
Por ejemplo, la partición P0 puede ser consumida por C1 del grupo de consumidores A y C3 del grupo de consumidores B.
Pero la partición P0 no puede ser consumida por C1 y C2, porque C1 y C2 están en el mismo grupo de consumidores.
inserte la descripción de la imagen aquí

7.1 De igual a igual

Todos los consumidores pertenecen al mismo grupo de consumidores.

Si llega un consumidor 4 en este momento, el
inserte la descripción de la imagen aquí
consumidor 4 puede consumir la partición P3
inserte la descripción de la imagen aquí
Si el consumidor 2 cuelga en este momento,
inserte la descripción de la imagen aquí
el consumidor 1 puede consumir la partición P1
inserte la descripción de la imagen aquí

7.2 Publicar Suscribirse

Cada consumidor pertenece a un grupo de consumidores diferente.
inserte la descripción de la imagen aquí

7.3 Partición y ordenación de mensajes

productor

  • Para los mensajes enviados por el mismo productor a la misma partición, el desplazamiento enviado primero es menor que el desplazamiento enviado después

inserte la descripción de la imagen aquí

Aquí offSet(M1) <offSer(M2)

  • No se puede garantizar el orden de los mensajes enviados por el mismo productor a diferentes particiones

inserte la descripción de la imagen aquí

Aquí, el mensaje M3 y el mensaje M4 se colocan en particiones diferentes, por lo que no se puede garantizar el tamaño del desplazamiento.

consumidor

  • Los consumidores consumen según el orden en que se almacenan los mensajes en la partición

inserte la descripción de la imagen aquí

Orden de consumo: M1, M2, M3

  • Kafka solo garantiza el orden de los mensajes dentro de una partición, no el orden de los mensajes entre particiones
    inserte la descripción de la imagen aquí

El orden de consumo aquí es M4, M1, M2, M3
, porque M4 y M1 son mensajes de diferentes particiones, por lo que no es seguro quién se consumirá primero.
Sin embargo, M1, M2 y M3 están en la misma partición, y el orden de consumo generalmente es seguro, pero no hay garantía de que otros mensajes se intercalarán en el medio.

Nota:
1. Configure una partición, de modo que se pueda garantizar el orden de todos los mensajes, pero se pierda la escalabilidad y el rendimiento.
2. Al configurar la clave del mensaje, los mensajes con la misma clave se enviarán a la misma partición.

8. Semántica de paso de mensajes

  • al menos una vez
    los mensajes no se pierden, pero pueden repetirse
  • A lo sumo
    se puede perder un mensaje, nunca reenviar
  • Exactamente una vez
    para garantizar que el mensaje se entregue al servidor y no se repita en el servidor

Tanto los productores como los consumidores están obligados a garantizar que

productor

(1) A lo sumo una vez

Independientemente de que el Broker lo haya recibido o no, se enviará una sola vez.
inserte la descripción de la imagen aquí

(2) Al menos una vez

El productor envía un mensaje y el Broker falla cuando recibe el recibo, entonces el productor piensa que el Broker no ha recibido el mensaje después de esperar un tiempo de espera y luego vuelve a enviar el mensaje.
inserte la descripción de la imagen aquí

consumidor

(1) A lo sumo una vez

El consumidor envía primero la posición de consumo, establece el desplazamiento + 1 y luego lee el mensaje. Si el proceso de lectura falla, el mensaje se perderá y no se podrá volver a leer.
inserte la descripción de la imagen aquí

(2) Al menos una vez

El consumidor primero lee el mensaje y luego envía la ubicación de consumo. Si falla durante el proceso de envío, la próxima vez que se lea el mensaje, el desplazamiento no cambiará y continuará leyendo el mensaje desde la vez anterior.
inserte la descripción de la imagen aquí

Exactamente una vez

Solo implementado en Kafka 0.11.0 y versiones posteriores

9. API de productor

Modelo de envío asíncrono

(1) Introducir dependencias

<dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.8.1</version>
        </dependency>

(2) Escribir código de productor

public class AvroProducer {
    
    
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9093");
        props.put("linger.ms", 1);
        props.put("key.serializer", StringSerializer.class.getName());
        props.put("value.serializer", AvroSerializer.class.getName());

        User user = User.newBuilder().setFavoriteNumber(1).setUserId(10001l).setName("jeff").setFavoriteColor("red").build();
        ProductOrder order = ProductOrder.newBuilder().setOrderId(2000l).setUserId(user.getUserId()).setProductId(101l).build();

        Producer<String, Object> producer = new KafkaProducer<>(props);
        // 发送user消息
        for (int i = 0; i < 10; i++) {
    
    
            Iterable<Header> headers = Arrays.asList(new RecordHeader("schema", user.getClass().getName().getBytes()));
            producer.send(new ProducerRecord<String, Object>("my-topic", null, "" + user.getUserId(), user, headers));
        }
        // 发送order消息
        for (int i = 10; i < 20; i++) {
    
    
            Iterable<Header> headers = Arrays.asList(new RecordHeader("schema", order.getClass().getName().getBytes()));
            producer.send(new ProducerRecord<String, Object>("my-topic", null, "" + order.getUserId(), order, headers));
        }

        System.out.println("send successful");
        producer.close();

    }
}

El productor utiliza principalmente send para enviar mensajes.Después
de que el productor coloca el mensaje en el búfer de la partición correspondiente, devuelve el resultado y continúa enviando el siguiente mensaje.
El subproceso de inicio en segundo plano entrega los mensajes en el búfer al Broker para su procesamiento.
inserte la descripción de la imagen aquí

(3).Ver el resultado

1. Inicializar la conexión
2. Al mismo tiempo, el productor coloca el mensaje en el búfer
3. Envía un mensaje de éxito después de crear la conexión

# 初始化连接
[2023-04-02 15:04:12,467] TRACE [main] Added sensor with name connections-created: (org.apache.kafka.common.metrics.Metrics)
# 与此同时生产者将消息放入缓冲区
[name=record-queue-time-avg, group=producer-metrics, description=The average time in ms record batches spent in the send buffer., tags={
    
    client-id=producer-1}] (org.apache.kafka.common.metrics.Metrics)
# 发送成功
[RecordHeader(key = schema, value = [111, 110, 101, 104, 111, 117, 114, 46, 107, 97, 102, 107, 97, 46, 101, 120, 97, 109, 112, 108, 101, 46, 97, 118, 114, 111, 46, 80, 114, 111, 100, 117, 99, 116, 79, 114, 100, 101, 114])], isReadOnly = true), key=10001, value={
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}, timestamp=null) with callback null to topic my-topic partition 0 (org.apache.kafka.clients.producer.KafkaProducer)
send successful

enviar sincrónicamente

Future<RecordMetadata> result = 
		producer.send(new ProducerRecord<String, String>("mytopic", "" + (i % 5), Integer.toString(i)));
		try {
    
       
			  RecordMetadata recordMetadata = result.get();
		} catch (ExecutionException e) {
    
        
			e.printStackTrace();
		}

el código se muestra a continuación:

 /**
     * 同步发送消息
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.10.17:9093");
        props.put("linger.ms", 1);
        props.put("key.serializer", StringSerializer.class.getName());
        props.put("value.serializer", AvroSerializer.class.getName());

        Producer<String, String> producer = new KafkaProducer<>(props);
        // 发送user消息
        for (int i = 0; i < 20; i++) {
    
    
            Future<RecordMetadata> result =
                    producer.send(new ProducerRecord<String, String>("mytopic", "" + (i % 5), Integer.toString(i)));
            try {
    
    
                RecordMetadata recordMetadata = result.get();
            } catch (ExecutionException | InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        System.out.println("send successful");
        producer.close();

    }

Resultado:
después de enviar un mensaje, se envía otro

[2023-04-02 15:16:43,381] DEBUG [kafka-producer-network-thread | producer-1] [Producer clientId=producer-1] Sending METADATA request with header RequestHeader(apiKey=METADATA, apiVersion=9, clientId=producer-1, correlationId=1) and timeout 30000 to node -1: MetadataRequestData(topics=[MetadataRequestTopic(topicId=AAAAAAAAAAAAAAAAAAAAAA, name='mytopic')], allowAutoTopicCreation=true, includeClusterAuthorizedOperations=false, includeTopicAuthorizedOperations=false) (org.apache.kafka.clients.NetworkClient)

[2023-04-02 15:16:43,382] DEBUG [kafka-producer-network-thread | producer-1] [Producer clientId=producer-1] Sending transactional request InitProducerIdRequestData(transactionalId=null, transactionTimeoutMs=2147483647, producerId=-1, producerEpoch=-1) to node 192.168.10.17:9093 (id: -1 rack: null) with correlation ID 2 (org.apache.kafka.clients.producer.internals.Sender)

Envío por lotes

 /**
     * 批量发送消息
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.10.17:9093");
        props.put("key.serializer", StringSerializer.class.getName());
        props.put("value.serializer", StringSerializer.class.getName());
        // 批量发送
        // 每一批消息最大大小
        props.put("batch.size", 16384);
        // 延迟时间
        props.put("linger.ms", 1000);

        Producer<String, String> producer = new KafkaProducer<>(props);
        // 发送user消息
        for (int i = 0; i < 20; i++) {
    
    
            producer.send(new ProducerRecord<String, String>("my-topic", Integer.toString(i), Integer.toString(i)));
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        System.out.println("send successful");
        producer.close();

    }

ver registro

[2023-04-02 15:29:25,218] TRACE [kafka-producer-network-thread | producer-1] Added sensor with name topic.my-topic.bytes (org.apache.kafka.common.metrics.Metrics)

[2023-04-02 15:29:25,218] TRACE [kafka-producer-network-thread | producer-1] Registered metric named MetricName [name=byte-total, group=producer-topic-metrics, description=The total number of bytes sent for a topic., tags={
    
    client-id=producer-1, topic=my-topic}] (org.apache.kafka.common.metrics.Metrics)

Atributo de reconocimiento y atributo de reintentos

Entre ellos, el atributo acks es muy importante, se explica de la siguiente manera:
acks: Es la notificación del mensaje
acks: -1 El líder y el seguidor han recibido el mensaje, o configurados en acks: todos los
acks: 0 Después de que el productor pone el mensaje en el búfer, devuelve directamente (como máximo una vez)
acuse de recibo: 1 El mensaje ha sido recibido por el líder, pero no se sabe si el seguidor se ha sincronizado

Aquí lo configuramos para todos
inserte la descripción de la imagen aquí
los reintentos: cuántas veces reintentar después de fallar, el valor predeterminado es 0.

  • como máximo una vez

reconocimientos = 0 o reconocimientos = 1

  • al menos una vez

acks = - 1/todos los reintentos > 0

10. API del consumidor

(1) Introducir dependencias

consistente con el productor

<dependency>
          <groupId>org.apache.kafka</groupId>
          <artifactId>kafka-clients</artifactId>
          <version>2.8.1</version>
</dependency>

Hay un tema en Kafka, __consumer_offsets,
que se usa para guardar qué tema y en qué partición consume el consumidor, qué posición de consumo
conduce a una recuperación rápida
y prevalecerá la posición comprometida.

Confirmación automática (hasta una vez)

enable.auto.commit: indica el envío automático
auto.commit.interval.ms: indica el envío automático cada milisegundo

props.put("enable.auto.commit", "true"); 
props.put("auto.commit.interval.ms", "1000"); 

Una vez que el consumidor sondea el mensaje, significa que se envía la posición de compensación

ConsumerRecords<String, String> records = consumer.poll(100); 

El código completo es el siguiente:

 /**
     * 自动提交(最多一次)
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.10.17:9093");
        props.setProperty("group.id", "group-1");
        props.setProperty("key.deserializer", StringDeserializer.class.getName());
        props.setProperty("value.deserializer", StringDeserializer.class.getName());
        // 支持自动提交
        props.setProperty("enable.auto.commit", "true");
        // 表示每隔多少秒自动提交一次
        props.setProperty("auto.commit.interval.ms", "1000");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
    
    
            ConsumerRecords<String, String> records = consumer.poll(100);
            // 打印消息
            for (ConsumerRecord<String, String> record : records) {
    
    
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }

Confirmar manualmente (al menos una vez)

desactivar la confirmación automática

props.put("enable.auto.commit", "false");

El envío manual debe llamarse con parámetros para el envío por lotes

consumer.commitSync();//批量提交

El código completo es el siguiente:

/**
     * 手动提交(至少一次)
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.10.17:9093");
        props.setProperty("group.id", "group-1");
        props.setProperty("key.deserializer", StringDeserializer.class.getName());
        props.setProperty("value.deserializer", StringDeserializer.class.getName());
        // 支持自动提交
        props.setProperty("enable.auto.commit", "false");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));
        final int minBatchSize = 20;
        List<ConsumerRecord<String, String>> buffer = new ArrayList<>();

        while (true) {
    
    
            ConsumerRecords<String, String> records = consumer.poll(100);
            // 打印消息
            for (ConsumerRecord<String, String> record : records) {
    
    
                buffer.add(record);
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
            if (buffer.size() >= minBatchSize) {
    
    
                consumer.commitSync();//批量提交
                buffer.clear();
            }

        }
    }

Pero si queremos agregar algunos elementos uno por uno, debemos agregar parámetros

long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                 consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));

El código completo es el siguiente:

    /**
     * 手动提交(至少一次)逐条提交
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.10.17:9093");
        props.setProperty("group.id", "group-1");
        props.setProperty("key.deserializer", StringDeserializer.class.getName());
        props.setProperty("value.deserializer", StringDeserializer.class.getName());
        // 支持自动提交
        props.setProperty("enable.auto.commit", "false");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));

        while (true) {
    
    
            ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
            for (TopicPartition partition : records.partitions()) {
    
    
                List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);

                for (ConsumerRecord<String, String> record : partitionRecords) {
    
    
                    System.out.println(record.offset() + ": " + record.value());
                }

                long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));
            }
        }
    }

Kafka también admite la especificación manual de particiones de consumo y ubicaciones de consumo
Especificación de particiones de consumo

String topic = "foo";
TopicPartition partition0 = new TopicPartition(topic, 0);
TopicPartition partition1 = new TopicPartition(topic, 1);
consumer. assign(Arrays.asList(partition0, partition1));

Lugar de consumo designado

seek(TopicPartition, long)

11. Exactamente una vez

productor

La configuración es la siguiente:
enable.idempotence: idempotence

props.setProperty("enable.idempotence", "true");
props.setProperty("acks", "all");

consumidor

No es una buena manera de evitar el doble consumo a través de la compensación.
Por lo general, se agrega una ID única (como ID de flujo, ID de orden) al mensaje. Cuando se procesan negocios, la ID se juzga para evitar el procesamiento repetido.

12. Mensajes transaccionales

Las transacciones deben satisfacer la atomicidad, ya sea que todas tengan éxito o todas fallen.
inserte la descripción de la imagen aquí
Ejemplo de código:

 /**
     * 事务
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.10.17:9093");
        props.put("transactional.id", "my-transactional-id");
        Producer<String, String> producer = new KafkaProducer<>(props, new StringSerializer(), new StringSerializer());
        producer.initTransactions();
        try {
    
    
            producer.beginTransaction();
            for (int i = 0; i < 100; i++) {
    
    
                producer.send(new ProducerRecord<String, String>("my-topic", Integer.toString(i), Integer.toString(i)));
            }
            producer.commitTransaction();
        } catch (ProducerFencedException | OutOfOrderSequenceException | AuthorizationException e) {
    
    
            // We can't recover from these exceptions, so our only option is to close the producer and exit.
            producer.close();
        } catch (KafkaException e) {
    
    
            // For al1 other exceptions, just abort the transaction and try again.
            producer.abortTransaction();

        } finally {
    
    
            producer.close();

        }
    }

Nivel de aislamiento de transacción
Isolation_level Nivel de aislamiento
El valor predeterminado es: read_uncommitted Lectura sucia
read_committed Lectura correcta de datos confirmados sin lectura sucia

Por ejemplo, configuramos de la siguiente manera:
Ejecutar en el directorio bin

cd /opt/Zookeeper/apache-zookeeper-3.5.6-bin/bin
./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic mytopic --isolation-level read_committed
./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic mytopic --isolation-level read_uncommitted

13. Serialización y Avro

Los objetos se transfieren entre redes en forma binaria o se guardan en archivos, y se pueden restaurar de acuerdo con reglas específicas.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

13.1 Ventajas de la serialización

1. Ahorre espacio y mejore la eficiencia de transmisión de la red
2. Multiplataforma
3. Multilingüe

Formato del mensaje de registro kafka
inserte la descripción de la imagen aquí

13.2 Nueve tipos de serialización proporcionados por kafka

  • Kafka proporciona 9 tipos básicos de serialización y deserialización, bajo el paquete org.apache.kafka.common.serialization
Publicación por entregas deserialización
ByteArraySerializer ByteArrayDeserializer
ByteBufferSerializer ByteBufferDeserializer
BytesSerializer BytesDeserializador
Serializador corto Deserializador corto
IntegerSerializerIntegerSerializer IntegerDeserializerIntegerDeserializer
LongSerializer LongDeserializer
FloatSerializer Deserializador flotante
Serializador doble Deserializador doble
StringSerializer StringDeserializer (String usa el conjunto de caracteres UTF8 de forma predeterminada)

13.3 Serialización personalizada

La serialización debe implementarse

Package org.apache.kafka.common.serialization
Interface Serializer<T>

La deserialización debe implementarse

Package org.apache.kafka.common.serialization
Interface Deserializer<T>

13.4 Formatos de mensajes comunes

  • CSV
    es bueno para mensajes simples

  • JSON
    es muy legible y ocupa mucho espacio,
    lo que lo hace adecuado para ElasticSearch

  • Mensaje serializado
    Avro: Hadoop, Hive admiten bien
    Protobuf

  • Serialización personalizada
    Avro y Schema

13.5 Uso de Avro

Generalmente combinado con big data, a menudo usamos Avro
(1) primero necesitamos introducir dependencias

<dependency>
      <groupId>org.apache.avro</groupId>
      <artifactId>avro</artifactId>
      <version>1.11.0</version>
</dependency>
<plugin>
                <groupId>org.apache.avro</groupId>
                <artifactId>avro-maven-plugin</artifactId>
                <version>1.11.0</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>schema</goal>
                        </goals>
                        <configuration>
                            <sourceDirectory>./src/main/avro/</sourceDirectory>
                            <outputDirectory>./src/main/java/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

(2) Defina un esquema
y cree un nuevo User.avsc
inserte la descripción de la imagen aquí

{
    
    "namespace": "onehour.kafka.example.avro",
  "type": "record",
  "name": "User",
  "fields": [
    {
    
    "name": "name", "type": "string"},
    {
    
    "name": "favorite_number",  "type": ["int", "null"]},
    {
    
    "name": "favorite_color", "type": ["string", "null"]}
  ]
}

(3) Ejecute el complemento
inserte la descripción de la imagen aquí
Generar después de que se ejecute el complemento:
inserte la descripción de la imagen aquí
copie esta clase en
inserte la descripción de la imagen aquí

(4) Escriba al productor

 /**
     * Avro发送消息
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.10.17:9093");
        props.put("linger.ms", 1);
        props.put("key.serializer", StringSerializer.class.getName());
        props.put("value.serializer", AvroSerializer.class.getName());

        User user = User.newBuilder()
                .setName("jeff")
                .setFavoriteColor("red")
                .setFavoriteNumber(7)
                .build();

        Producer<String, Object> producer = new KafkaProducer<>(props);
        // 发送user消息
        for (int i = 0; i < 10; i++) {
    
    
            producer.send(new ProducerRecord<String, Object>("my-topic", Integer.toString(i), user));
        }

        System.out.println("send successful");
        producer.close();

    }

(5) Debe escribir su propia clase para implementar la interfaz Serializer para la serialización
El código AvroSerializer.java es el siguiente

package onehour.kafka.example.serialization;

import onehour.kafka.example.avro.v1.User;
import org.apache.avro.message.BinaryMessageEncoder;
import org.apache.kafka.common.header.Header;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class AvroSerializer implements Serializer {
    
    

    public static final StringSerializer Default = new StringSerializer();
    private static final Map ENCODERS = new HashMap();

    @Override
    public void configure(Map map, boolean b) {
    
    

    }

    @Override
    /**
     * 根据topic对应的类型序列化
     */
    public byte[] serialize(String topic, Object o) {
    
    
        if (topic.equals("my-topic")) {
    
    
            try {
    
    
                return User.getEncoder().encode((User) o).array();
            } catch (IOException e) {
    
    
                throw new RuntimeException(e);
            }
        }

        return Default.serialize(topic, o.toString());
    }

    @Override
    public void close() {
    
    

    }
}


(6) Debe escribir su propia clase para implementar la interfaz Deserializer para deserializar
el código AvroDeserializer de la siguiente manera

package onehour.kafka.example.serialization;

import onehour.kafka.example.avro.v1.User;
import org.apache.avro.message.BinaryMessageDecoder;
import org.apache.kafka.common.header.Header;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class AvroDeserializer implements Deserializer {
    
    

    public static final StringDeserializer Default = new StringDeserializer();
    private static final Map DECODERS = new HashMap<>();

    @Override
    public void configure(Map map, boolean b) {
    
    

    }

    @Override
    /**
     * 根据topic对应的类型反序列化
     */
    public Object deserialize(String topic, byte[] bytes) {
    
    
        if (topic.equals("my-topic")) {
    
    
            try {
    
    
                return User.getDecoder().decode(bytes);
            } catch (IOException e) {
    
    
                throw new RuntimeException(e);
            }
        }

        return Default.deserialize(topic, bytes);
    }

    @Override
    public void close() {
    
    

    }
}

(7) Escribir consumidores

 /**
     * Avro
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.10.17:9093");
        props.setProperty("group.id", "test");
        props.setProperty("enable.auto.commit", "true");
        props.setProperty("auto.commit.interval.ms", "1000");
        props.setProperty("key.deserializer", StringDeserializer.class.getName());
        props.setProperty("value.deserializer", AvroDeserializer.class.getName());

        KafkaConsumer<String, User> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
    
    
            ConsumerRecords<String, User> records = consumer.poll(100);
            // 打印消息
            for (ConsumerRecord<String, User> record : records) {
    
    
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
            }
        }
    }

Resultado: Consumidor

offset = 241, key = 0, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 242, key = 1, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 243, key = 2, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 244, key = 3, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 245, key = 4, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 246, key = 5, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 247, key = 6, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 248, key = 7, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 249, key = 8, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}
offset = 250, key = 9, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red"}

14. Uso del encabezado de registro

inserte la descripción de la imagen aquí
Si sigue la explicación anterior, cada tema corresponde a una clase de entidad y puede usar el nombre del tema al escribir el método de serialización.
inserte la descripción de la imagen aquí
Pero la realidad a menudo no es el caso.
Ejemplo:
después de que un nuevo usuario se registra con éxito, compra un pedido y luego cancela el pedido.
El orden entre estos eventos es importante.
Kafka no garantiza el orden entre particiones. Si el mensaje de cancelación del pedido es anterior al registro del usuario o la compra del producto, habrá un problema con la lógica de procesamiento.
inserte la descripción de la imagen aquí
La solución es la siguiente:
En este caso.
Para garantizar un consumo fluido, todos los eventos se colocan en la misma partición del mismo tema. Por lo tanto, use la ID de usuario como clave de partición para que estén en la misma partición.
inserte la descripción de la imagen aquí
En este caso, habrá varios tipos diferentes de particiones en el mismo tema y habrá problemas para juzgar el método de serialización de acuerdo con el nombre del tema en el método anterior.

Confluent ofrece una solución: Registro de esquemas.

  • Los productores envían el esquema y la estructura del mensaje al Registro antes de enviar el mensaje. El registro devuelve una identificación y los datos en sí mismos a Kafka.
  • Después de que los consumidores reciben el mensaje, primero leen la identificación, luego analizan el esquema del mensaje (esquema) en el Registro y luego obtienen los datos a través del esquema.

Desventajas:
1. El análisis de datos depende en gran medida del registro del esquema
2. Destruye los datos en sí
inserte la descripción de la imagen aquí

Otra solución es introducir Record Header
inserte la descripción de la imagen aquí
para modificar el código y agregar el pedido avsc
product_order.avsc
inserte la descripción de la imagen aquí

{
    
    
  "namespace": "onehour.kafka.example.avro",
  "type": "record",
  "name": "ProductOrder",
  "fields": [
    {
    
    "name": "order_id", "type": "long"},
    {
    
    "name": "product_id", "type": "long"},
    {
    
    "name": "user_id", "type": "long"}
  ]
}

Modifique el usuario, agregue el código Userid
User.v1.avsc
inserte la descripción de la imagen aquí
de la siguiente manera:

{
    
    "namespace": "onehour.kafka.example.avro.v1",
  "type": "record",
  "name": "User",
  "fields": [
    {
    
    "name": "name", "type": "string"},
    {
    
    "name": "favorite_number",  "type": ["int", "null"]},
    {
    
    "name": "favorite_color", "type": ["string", "null"]},
    {
    
    "name": "user_id", "type": "long"}
  ]
}

Use complementos para generar clases de entidad,
inserte la descripción de la imagen aquí
copiar el pasado
inserte la descripción de la imagen aquí
y modificar el código del productor

    /**
     * Avro发送消息(Record Header)
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.10.15:9093");
        props.put("linger.ms", 10);
        props.put("key.serializer", StringSerializer.class.getName());
        props.put("value.serializer", AvroSerializer.class.getName());

        onehour.kafka.example.avro.v1.User user = User.newBuilder()
                .setUserId(10001L)
                .setName("jeff")
                .setFavoriteColor("red")
                .setFavoriteNumber(7)
                .build();
        ProductOrder order = ProductOrder.newBuilder().setOrderId(2000L).setUserId(user.getUserId()).setProductId(101L).build();

        Producer<String, Object> producer = new KafkaProducer<>(props);
        // 发送user消息
        for (int i = 0; i < 10; i++) {
    
    
            Iterable<Header> headers = Arrays.asList(new RecordHeader("schema", user.getClass().getName().getBytes()));
            producer.send(new ProducerRecord<String, Object>("my-topic", null, "" + user.getUserId(), user, headers));
        }
        // 发送order消息
        for (int i = 10; i < 20; i++) {
    
    
            Iterable<Header> headers = Arrays.asList(new RecordHeader("schema", order.getClass().getName().getBytes()));
            producer.send(new ProducerRecord<String, Object>("my-topic", null, "" + order.getUserId(), order, headers));
        }

        System.out.println("send successful");
        producer.close();

    }

Cambie la clase de serialización
AvroSerializerHeader.java
Aquí creé una nueva clase

package onehour.kafka.example.serialization;

import onehour.kafka.example.avro.User;
import org.apache.avro.message.BinaryMessageEncoder;
import org.apache.kafka.common.header.Header;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.ExtendedSerializer;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class AvroSerializerHeader implements ExtendedSerializer {
    
    

    public static final StringSerializer Default = new StringSerializer();
    private static final Map ENCODERS = new HashMap();

    @Override
    public void configure(Map map, boolean b) {
    
    

    }

    @Override
    /**
     * 根据topic对应的类型序列化
     */
    public byte[] serialize(String topic, Object o) {
    
    
        if (topic.equals("my-topic")) {
    
    
            try {
    
    
                return User.getEncoder().encode((User) o).array();
            } catch (IOException e) {
    
    
                throw new RuntimeException(e);
            }
        }

        return Default.serialize(topic, o.toString());
    }

    @Override
    public void close() {
    
    

    }

    @Override
    /**
     * 使用header中的schema信息进行序列化
     */
    public byte[] serialize(String topic, Headers headers, Object o) {
    
    

        if (o == null) {
    
    
            return null;
        }

        // 从header中读取schema
        String className = null;
        for (Header header : headers) {
    
    
            if (header.key().equals("schema")) {
    
    
                className = new String(header.value());
            }
        }

        // 使用schema中的className进行序列化
        if (className != null) {
    
    
            try {
    
    
                BinaryMessageEncoder encoder = (BinaryMessageEncoder) ENCODERS.get(className);
                if (encoder == null) {
    
    
                    Class cls = Class.forName(className);
                    Method method = cls.getDeclaredMethod("getEncoder");
                    encoder = (BinaryMessageEncoder) method.invoke(cls);
                    ENCODERS.put(className, encoder);
                }
                return encoder.encode(o).array();
            } catch (Exception e) {
    
    
                throw new RuntimeException(e);
            }
        }

        // 如果header中没有schema信息,则根据topic对应的类型进行序列化
        return this.serialize(topic, o);
    }
}

Cambiar la clase de deserialización
Aquí creé una nueva clase
AvroDeserializerHeader.java

package onehour.kafka.example.serialization;

import onehour.kafka.example.avro.User;
import org.apache.avro.message.BinaryMessageDecoder;
import org.apache.kafka.common.header.Header;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.ExtendedDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class AvroDeserializerHeader implements ExtendedDeserializer {
    
    

    public static final StringDeserializer Default = new StringDeserializer();
    private static final Map DECODERS = new HashMap<>();

    @Override
    public void configure(Map map, boolean b) {
    
    

    }

    @Override
    /**
     * 根据topic对应的类型反序列化
     */
    public Object deserialize(String topic, byte[] bytes) {
    
    
        if (topic.equals("my-topic")) {
    
    
            try {
    
    
                return User.getDecoder().decode(bytes);
            } catch (IOException e) {
    
    
                throw new RuntimeException(e);
            }
        }

        return Default.deserialize(topic, bytes);
    }

    @Override
    public void close() {
    
    

    }

    //@Override

    /**
     * 使用header中的schema信息进行反序列化
     */
    @Override
    public Object deserialize(String topic, Headers headers, byte[] bytes) {
    
    
        if (bytes == null) {
    
    
            return null;
        }

        // 从header中读取schema
        String className = null;
        for (Header header : headers) {
    
    
            if (header.key().equals("schema")) {
    
    
                className = new String(header.value());
            }
        }

        // 使用schema中的className进行反序列化
        if (className != null) {
    
    
            try {
    
    
                BinaryMessageDecoder decoder = (BinaryMessageDecoder) DECODERS.get(className);
                if (decoder == null) {
    
    
                    Class cls = Class.forName(className);
                    Method method = cls.getDeclaredMethod("getDecoder");
                    decoder = (BinaryMessageDecoder) method.invoke(cls);
                    DECODERS.put(className, decoder);
                }
                return decoder.decode(bytes);
            } catch (Exception e) {
    
    
                throw new RuntimeException(e);
            }
        }

        // 如果header中没有schema信息,则根据topic对应的类型反序列化
        return this.deserialize(topic, bytes);
    }
}

modificar consumidor

/**
     * Avro
     *
     * @param args
     */
    public static void main(String[] args) {
    
    
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "192.168.10.15:9093");
        props.setProperty("group.id", "test");
        props.setProperty("enable.auto.commit", "true");
        props.setProperty("auto.commit.interval.ms", "1000");
        props.setProperty("key.deserializer", StringDeserializer.class.getName());
        props.setProperty("value.deserializer", AvroDeserializerHeader.class.getName());

        KafkaConsumer<String, User> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("my-topic"));
        while (true) {
    
    
            ConsumerRecords<String, User> records = consumer.poll(100);
            // 打印消息
            for (ConsumerRecord<String, User> record : records) {
    
    
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
                for (Header header : record.headers()) {
    
    
                    System.out.println("headers -->" + header.key() + ":" + new String(header.value()));
                }
            }
        }
    }

resultado de la operación:

offset = 251, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 252, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 253, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 254, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 255, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 256, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 257, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 258, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 259, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 260, key = 10001, value = {
    
    "name": "jeff", "favorite_number": 7, "favorite_color": "red", "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.v1.User
offset = 261, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 262, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 263, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 264, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 265, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 266, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 267, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 268, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 269, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}
headers -->schema:onehour.kafka.example.avro.ProductOrder
offset = 270, key = 10001, value = {
    
    "order_id": 2000, "product_id": 101, "user_id": 10001}

Aquí la versión 1.0 implementa ExtendedSerializer, y
la versión 3.0 de ExtendedDeserializer implementa Serializer y Deserializer
inserte la descripción de la imagen aquí

15. Introducción al modo Kafka KRaft

La versión 2.8 de Kafka presenta una mejora importante: el modo KRaft. Esta característica ha estado en la etapa experimental.
El 3 de octubre de 2022, se lanzó Kafka 3.3.1 , declarando oficialmente que el modo KRaft se puede usar en un entorno de producción.
En el modo KRaft, todos los metadatos del clúster se almacenan en los temas internos de Kafka, que son administrados por el propio Kafka y ya no dependen de zookeeper.


El patrón KRaft tiene muchas ventajas:

  • Implementación y administración de clústeres simplificada : ya no se requiere Zookeeper, lo que simplifica la implementación y administración de los clústeres de Kafka. La huella de recursos es más pequeña.
  • Escalabilidad y resiliencia mejoradas : la cantidad de particiones en un solo clúster puede escalar a millones. Tiempos más rápidos de reinicio del clúster y recuperación de fallas.
  • Propagación de metadatos más eficiente : la propagación de metadatos basada en registros y basada en eventos mejora el rendimiento de muchas funciones principales de Kafka.

Actualmente, KRaft solo es aplicable a nuevos clústeres. Para migrar un clúster existente del modo zookeeper al modo KRaft , debe esperar a la versión 3.5 .
3.5 es una versión puente que dejará oficialmente obsoleto el modo cuidador del zoológico .
Kafka 4.0 (que se lanzará en agosto de 2023) eliminará por completo el modo cuidador del zoológico y solo admitirá el modo KRaft .

Nota: Hay errores importantes en Kafka 3.3.0 y se recomienda no usarlo.

15.1 Implementación de KRaft

(1) Implementación de un solo nodo

  • Generar uuid de clúster

Utilice las herramientas proporcionadas por kafka

./bin/kafka-storage.sh random-uuid
# 输入结果如下
# xtzWWN4bTjitpL3kfd9s5g

También puede generarlo usted mismo.El uuid del clúster kafka debe estar codificado en base64 con 16 bytes, y la longitud es de 22

#集群的uuid应为16个字节的base64编码,长度为22
echo -n "1234567890abcdef" | base64 | cut -b 1-22
# MTIzNDU2Nzg5MGFiY2RlZg
  • formato de directorio de almacenamiento
./bin/kafka-storage.sh format -t xtzWWN4bTjitpL3kfd9s5g \
                       -c ./config/kraft/server.properties
# Formatting /tmp/kraft-combined-logs

NOTA: Si instala varios nodos, será necesario formatear cada nodo.

  • empezar kafka
./bin/kafka-server-start.sh ./config/kraft/server.properties
  • archivo de configuración
# The role of this server. Setting this puts us in KRaft mode
process.roles=broker,controller
# The node id associated with this instance's roles
node.id=1
# The connect string for the controller quorum
controller.quorum.voters=1@localhost:9093
# Combined nodes (i.e. those with `process.roles=broker,controller`) must list the controller listener here at a minimum.
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
# Name of listener used for communication between brokers.
inter.broker.listener.name=PLAINTEXT
# 如果要从别的主机访问,将localhost修改为你的主机IP
advertised.listeners=PLAINTEXT://localhost:9092
# This is required if running in KRaft mode.
controller.listener.names=CONTROLLER
# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL
# A comma separated list of directories under which to store log files
log.dirs=/tmp/kraft-combined-logs

(2).docker componer despliegue

inserte la descripción de la imagen aquí

En el modo Kraft, los nodos del clúster se pueden configurar como controladores o intermediarios, o pueden desempeñar ambos roles al mismo tiempo.
El intermediario es responsable de procesar las solicitudes de mensajes y almacenar los registros de partición de temas, y el controlador es responsable de administrar los metadatos y ordenar al intermediario que responda de acuerdo con los cambios en los metadatos.
Los controladores solo ocupan una pequeña parte del clúster, generalmente un número impar (1, 3, 5, 7), y no pueden tolerar más de la mitad de las fallas de los nodos.

# kraft通用配置
x-kraft: &common-config
  ALLOW_PLAINTEXT_LISTENER: yes
  KAFKA_ENABLE_KRAFT: yes
  KAFKA_KRAFT_CLUSTER_ID: MTIzNDU2Nzg5MGFiY2RlZg
  KAFKA_CFG_PROCESS_ROLES: broker,controller
  KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
  KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: BROKER:PLAINTEXT,CONTROLLER:PLAINTEXT
  KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka-1:9091,2@kafka-2:9091,3@kafka-3:9091
  KAFKA_CFG_INTER_BROKER_LISTENER_NAME: BROKER

# 镜像通用配置
x-kafka: &kafka
  image: 'bitnami/kafka:3.3.1'
  networks:
    net:

# 自定义网络
networks:
  net:

# project名称
name: kraft
services:
  
  # combined server
  kafka-1:
    <<: *kafka
    container_name: kafka-1
    ports:
      - '9092:9092'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 1
      KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://:9092
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://10.150.36.72:9092 #宿主机IP

  kafka-2:
    <<: *kafka
    container_name: kafka-2
    ports:
      - '9093:9093'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 2
      KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://:9093
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://10.150.36.72:9093 #宿主机IP

  kafka-3:
    <<: *kafka
    container_name: kafka-3
    ports:
      - '9094:9094'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 3
      KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://:9094
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://10.150.36.72:9094 #宿主机IP

  #broker only
  kafka-4:
    <<: *kafka
    container_name: kafka-4
    ports:
      - '9095:9095'
    environment:
      <<: *common-config
      KAFKA_CFG_BROKER_ID: 4
      KAFKA_CFG_PROCESS_ROLES: broker
      KAFKA_CFG_LISTENERS: BROKER://:9095
      KAFKA_CFG_ADVERTISED_LISTENERS: BROKER://10.150.36.72:9095

Nota: 1. Si se implementa en un servidor o en una nube pública, realice los siguientes cambios:

KAFKA_CFG_LISTENERS: CONTROLLER://:9091,BROKER://0.0.0.0:9092
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://服务器IP或公务IP:9092

(3) Ver metadatos

# 创建主题
docker run -it --rm --network=kraft_net \
           bitnami/kafka:3.3.1 \
           /opt/bitnami/kafka/bin/kafka-topics.sh \
           --bootstrap-server kafka-1:9092,kafka-2:9093 \
           --create --topic my-topic \
           --partitions 3 --replication-factor 2

# 生产者
docker run -it --rm --network=kraft_net \
           bitnami/kafka:3.3.1 \
           /opt/bitnami/kafka/bin/kafka-console-producer.sh \
           --bootstrap-server kafka-1:9092,kafka-2:9093 \
           --topic my-topic

# 消费者
docker run -it --rm --network=kraft_net \
           bitnami/kafka:3.3.1 \
           /opt/bitnami/kafka/bin/kafka-console-consumer.sh \
           --bootstrap-server kafka-1:9092,kafka-2:9093 \
           --topic my-topic

# 查看元数据分区
docker run -it --rm --network=kraft_net \
           bitnami/kafka:3.3.1 \
           /opt/bitnami/kafka/bin/kafka-metadata-quorum.sh \
           --bootstrap-server kafka-1:9092,kafka-2:9093 \
           describe --status

#查看元数据副本
docker run -it --rm --network=kraft_net \
           bitnami/kafka:3.3.1 \
           /opt/bitnami/kafka/bin/kafka-metadata-quorum.sh \
           --bootstrap-server kafka-1:9092,kafka-2:9093 \
           describe --replication

# 查看元数据
# 元数据存储在每个节点上,可以在任意节点上查看
docker exec -it kafka-1 \
            /opt/bitnami/kafka/bin/kafka-metadata-shell.sh  \
           --snapshot /bitnami/kafka/data/__cluster_metadata-0/00000000000000000000.log

15.2 El camino al cuidador del zoológico

Desde el comienzo del nacimiento de Kafka, Zookeeper es inseparable Con el desarrollo de Kafka, las desventajas de Zookeeper fueron surgiendo gradualmente.
Al principio, Kafka almacenaba tanto los metadatos como la ubicación de consumo del consumidor ( offset offset) en zookeeper.

(1) Gestión de compensaciones

La ubicación de consumo son datos actualizados con frecuencia. Para zookeeper, la operación de escritura es costosa y las escrituras frecuentes pueden causar problemas de rendimiento. Todas las operaciones de escritura se entregan al líder para su ejecución y no se pueden escalar horizontalmente.
A partir de la versión 0.8.2 , la ubicación de consumo del consumidor ya no se escribe en zookeeper, sino que se registra __comsumer_offsetsen , y se crean 50 particiones de forma predeterminada, con <consumer group.id, topic, deployment number> como el clave de mensaje, las solicitudes pueden ser procesadas por múltiples intermediarios al mismo tiempo, por lo que tiene un mayor rendimiento de escritura y escalabilidad. Al mismo tiempo, kafka almacena en caché la vista de la última ubicación de consumo en la memoria, que puede leer rápidamente el desplazamiento.

(2) Gestión de metadatos

modo cuidador del zoológico

Antes de Kafka 3.3.0, los metadatos se almacenaban en zookeeper, con la siguiente estructura:
inserte la descripción de la imagen aquí

Cada clúster tiene un intermediario como controlador. El controlador no solo realiza el trabajo del intermediario, sino que también mantiene los metadatos del clúster, como la identificación del intermediario, el tema, la partición, el líder y el conjunto de réplicas sincrónicas (ISR), y otra información. El controlador guarda esta información en ZooKeeper, y la mayor parte del tráfico de lectura y escritura de ZooKeeper lo realiza el controlador. Cuando los metadatos cambian, el controlador propaga los metadatos más recientes a otros intermediarios.
inserte la descripción de la imagen aquí

Nota: Cada corredor puede comunicarse directamente con zookeeper. La figura anterior omite otras conexiones.
Por ejemplo, cuando se inicia el intermediario, se creará un nodo temporal /brokers/ids/{id} en zookeeper, y el líder de cada partición también actualizará la información del conjunto de réplicas (ISR) que se está sincronizando.

Zookeeper es equivalente al sistema de órdenes de trabajo, el controlador es el administrador del sistema de órdenes de trabajo, responsable de organizar el trabajo, y el corredor es responsable del trabajo, utilizando el sistema de trabajo de ángulo AB (líder, seguidor).
El controlador tiene las siguientes funciones:

  • Supervise si el corredor está vivo (el corredor registra en zookeeper y se conecta, y el controlador cuenta la cantidad de personas en línea)
  • Si cambia el tema, la partición, la réplica o el intermediario, si es necesario, seleccione un nuevo líder para la partición y actualice la lista de seguidores (la orden de trabajo o el cambio de personal, el controlador reasigna el trabajo)
  • Use la solicitud RPC para notificar a los corredores relevantes para que se conviertan en líderes o seguidores (notifique al personal relevante para comenzar a trabajar)
  • Escriba los últimos metadatos en zookeeper y envíelos a otros intermediarios (actualice el sistema de órdenes de trabajo e informe al resto del personal sobre los últimos arreglos de trabajo)

Nota: La selección de un nuevo líder no se basa en la votación, pero el primero en el conjunto de ISR se selecciona como líder. Este tipo de selección es más tolerante a fallas. Por ejemplo, en el caso de 2N+1 réplicas, como máximo 2N réplicas pueden fallar y el método de elección solo puede permitir como máximo N fallas.

pregunta

  • A medida que la cantidad de nodos y particiones crece linealmente, los metadatos se vuelven cada vez más grandes y el controlador tarda más en propagar los metadatos al intermediario.
  • ZooKeeper no es adecuado para almacenar grandes cantidades de datos y los cambios frecuentes de datos pueden provocar cuellos de botella en el rendimiento. Además, los límites de tamaño de Znode y el número máximo de observadores pueden ser restricciones.
  • Los metadatos se almacenan en ZooKeeper. Cada corredor obtiene los metadatos más recientes del controlador y los almacena en caché en su propia memoria. Cuando las actualizaciones se retrasan o se reordenan, los datos pueden ser inconsistentes y se requieren controles de verificación adicionales para garantizar la coherencia de los datos.
  • Cuando el controlador falla o se reinicia, el nuevo controlador necesita volver a extraer todos los metadatos de Zookeeper. Cuando hay muchas particiones en el clúster (cientos de miles o incluso millones), el tiempo para cargar los metadatos será lento. Se vuelve muy largo. , durante el cual el controlador no puede responder y trabajar, lo que afectará la disponibilidad de todo el clúster.

Nota: Cuando el controlador falla o se reinicia, se notificará a otros intermediarios como observadores. Cada intermediario intenta crear un nodo /controller en ZooKeeper. Quien lo cree primero se convertirá en el nuevo controlador.

15.3 Modo Kraft

inserte la descripción de la imagen aquí

(1) Gestión de metadatos

inserte la descripción de la imagen aquí

Basado en el protocolo de consenso de Raft, KRaft elige un controlador activo a través del mecanismo de arbitraje (quorom).Todas las operaciones de escritura de metadatos son manejadas por el controlador activo, y el controlador activo escribe registros de cambio de metadatos en el tema __cluster_metadatainterno el orden de escritura, este tema tiene solo una partición. El controlador principal es el líder de esta partición, y otros controladores actúan como seguidores para sincronizar los datos con el registro local. Después de que más de la mitad de los controladores estén sincronizados, entonces es considera que la escritura de datos es exitosa y el controlador maestro devuelve un mensaje al cliente.
Todos los controladores almacenan en caché los registros de metadatos locales en la memoria y los mantienen actualizados dinámicamente. Cuando el controlador maestro falla, otros controladores pueden convertirse inmediatamente en el nuevo controlador maestro y asumir el control en cualquier momento.
Además del controlador, cada intermediario, como observador (Observador), también sincroniza los metadatos con una copia local y los almacena en caché en la memoria.

docker run -it --rm --network=kraft_net \
           bitnami/kafka:3.3.1 \
           /opt/bitnami/kafka/bin/kafka-metadata-quorum.sh \
           --bootstrap-server kafka-1:9092,kafka-2:9093 \
           describe --replication

inserte la descripción de la imagen aquí

ver metadatos

docker exec -it kafka-1 bash
ls
cd topics/
ls
cat my-topic/0/data

inserte la descripción de la imagen aquí

El método de propagación de metadatos se cambia de la solicitud RPC original al registro de metadatos síncrono. No hay necesidad de preocuparse por las diferencias de datos. Las vistas materializadas de metadatos locales de cada corredor eventualmente serán consistentes porque provienen del mismo registro. También podemos rastrear y eliminar fácilmente las diferencias a través de marcas de tiempo y compensaciones.
inserte la descripción de la imagen aquí

El controlador y el intermediario escribirán periódicamente instantáneas de metadatos en la memoria en el archivo del punto de control (punto de control). El nombre del archivo del punto de control contiene la última ubicación de consumo de la instantánea y la ID del controlador. Cuando reiniciamos el controlador o el intermediario, no es necesario lea desde el principio Obtenga metadatos, cargue directamente el último archivo de punto de control local en la memoria y luego comience a leer los datos del área desde la última posición de consumo en el archivo de punto de control, lo que acorta el tiempo de inicio.

15.4 Selección de versión

Actualmente, KRaft solo es aplicable a nuevos clústeres. Para migrar un clúster existente del modo zookeeper al modo KRaft, debe esperar a la versión 3.5.
3.5 es una versión puente que dejará oficialmente obsoleto el modo cuidador del zoológico.
Kafka 4.0 (que se lanzará en agosto de 2023) eliminará por completo el modo cuidador del zoológico y solo admitirá el modo KRaft.
Nota: Hay errores importantes en la versión Kafka 3.3.0, se recomienda no usarla.

Supongo que te gusta

Origin blog.csdn.net/sinat_38316216/article/details/129898946
Recomendado
Clasificación