Inicio rápido de kafka Sigue las notas del curso intensivo de una hora de kafka en la estación B
- 1. Origen y desarrollo de Kafka
- 2. Colas de mensajes comunes
- 3. Temas, particiones, réplicas, intermediarios de mensajes
- 4. Construcción del entorno - local pseudo-distribuido
- 5. Oyentes y redes internas y externas
- 6. Construcción del entorno: despliegue de la ventana acoplable kafka⭐
- 7. Modelo de mensaje y secuencia de mensajes
- 8. Semántica de paso de mensajes
- 9. API de productor
- 10. API del consumidor
- 11. Exactamente una vez
- 12. Mensajes transaccionales
- 13. Serialización y Avro
- 14. Uso del encabezado de registro
- 15. Introducción al modo Kafka KRaft
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
origen:
- 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
(2) Tema - Publicar Suscríbete
(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
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.
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.
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.
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.
Cuando no se especifica ninguna clave, Kafka escribe mensajes en diferentes particiones en forma de sondeo.
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.
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-factor
el número de réplicas.
Ejemplo: aquí el factor de replicación = 3 significa que hay 3 particiones en total.
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
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.
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.
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:
Construir en linux
Adjuntar dirección:
kafka descargar
Código de extracción: nmrs
Después de la descarga, extráigalo al /opt
directorio
tar -zxvf kafka_2.11-2.4.1.tgz
Kafka depende de zookeeper.
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
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
copiar archivo de configuración
cp config/zookeeper.properties etc
Ver el contenido del archivo de configuración
vim zookeeper.properties
La configuración es la siguiente:
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
Ingrese al directorio etc.
cd etc
vim server-0.properties
La configuración es la siguiente:
Crear un directorio de registro
Configurar la ubicación del registro
log.dirs=/opt/kafka_2.11-2.4.1/logs/kafka-logs-0
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
el directorio de registro:
log.dirs=/opt/kafka_2.11-2.4.1/logs/kafka-logs-1
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
el directorio de registro:
log.dirs=/opt/kafka_2.11-2.4.1/logs/kafka-logs-2
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
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
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
Verifique el estado de partición del tema
./kafka-topics.sh --zookeeper localhost:2181 --describe --topic test
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
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
y el consumidor escucha con éxito
5. Oyentes y redes internas y externas
Configuración del archivo de configuración
Configuración de escucha:
5.1 Configuración y acceso al listener (intranet)
5.2 Escenario de configuración del servidor Aliyun (combinación de red externa y red interna)
Esta vez el
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
Visite
kafka-github
El contenido del docker-compose.yml copiado
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
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 \
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.
7.1 De igual a igual
Todos los consumidores pertenecen al mismo grupo de consumidores.
Si llega un consumidor 4 en este momento, el
consumidor 4 puede consumir la partición P3
Si el consumidor 2 cuelga en este momento,
el consumidor 1 puede consumir la partición P1
7.2 Publicar Suscribirse
Cada consumidor pertenece a un grupo de consumidores diferente.
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
Aquí offSet(M1) <offSer(M2)
- No se puede garantizar el orden de los mensajes enviados por el mismo productor a diferentes particiones
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
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
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.
(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.
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.
(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.
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.
(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
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.
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.
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
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
{
"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
Generar después de que se ejecute el complemento:
copie esta clase en
(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
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.
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.
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.
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í
Otra solución es introducir Record Header
para modificar el código y agregar el pedido avsc
product_order.avsc
{
"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
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,
copiar el pasado
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
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
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_offsets
en , 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:
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.
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
(1) Gestión de metadatos
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_metadata
interno 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
ver metadatos
docker exec -it kafka-1 bash
ls
cd topics/
ls
cat my-topic/0/data
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.
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.