Solución:
La secuencia de Kafka ofrece la semántica exactamente una vez de la de extremo a extremo punto de vista (consume de un tema, procesa ese mensaje y luego produce para otro tema). Sin embargo, mencionaste solo el del productor Atributo idempotente. Esa es solo una pequeña parte de la imagen completa.
Deja que exprese esa pregunta de otra manera:
¿Por qué necesitamos la semántica de entrega exactamente una vez en el lado del consumidor mientras que ya hemos garantizado la semántica de entrega exactamente una vez en el lado del productor?
Respuesta: Dado que la semántica de entrega exactamente una vez no solo se encuentra en el paso de producción, sino en el flujo completo de procesamiento. Para lograr la entrega exactamente una vez semánticamente, hay algunas condiciones que deben cumplirse con la producción y el consumo.
Este es el escenario genérico: el proceso A produce mensajes para el tema T. Al mismo tiempo, el proceso B intenta consumir mensajes del tema T. Queremos asegurarnos de que el proceso B nunca procese un mensaje dos veces.
Parte del productor: Debemos asegurarnos de que los productores nunca produzcan un mensaje dos veces. Podemos utilizar Kafka Idempotent Producer
Parte del consumidor:
Este es el flujo de trabajo básico para el consumidor:
- Paso 1: el consumidor extrae el mensaje M correctamente del tema de Kafka.
- Paso 2: el consumidor intenta ejecutar el trabajo y el trabajo se devuelve correctamente.
- Paso 3: el consumidor confirma la compensación del mensaje a los corredores de Kafka.
Los pasos anteriores son solo un camino feliz. Hay muchos problemas que surgen en la realidad.
- Escenario 1: el trabajo del paso 2 se ejecuta correctamente, pero luego el consumidor se bloquea. Desde esta circunstancia inesperada, el consumidor aún no ha comprometido la compensación del mensaje. Cuando el consumidor se reinicia, el mensaje se consumirá dos veces.
- Escenario 2: mientras el consumidor confirma el desplazamiento en el paso 3, se bloquea debido a fallas de hardware (por ejemplo: CPU, violación de memoria, …) Al reiniciar, el consumidor no puede saber si ha cometido el desplazamiento correctamente o no.
Debido a que pueden ocurrir muchos problemas, la ejecución del trabajo y el desplazamiento de confirmación deben ser atómico para garantizar la semántica de entrega exactamente una vez en el lado del consumidor. No significa que no podamos, pero se necesita un gran esfuerzo para asegurarnos de la semántica de entrega exactamente una vez. Kafka Stream respalda el trabajo de los ingenieros.
Observó que: Kafka Stream ofrece “procesamiento de flujo exactamente una vez”. Se refiere a consumir de un tema, materializar un estado intermedio en un tema de Kafka y producir a uno. Si nuestra aplicación depende de otros servicios externos (base de datos, servicios …), debemos asegurarnos de que nuestras dependencias externas puedan garantizar exactamente una vez en esos casos.
TL, DR: exactamente una vez para el flujo completo se necesita la cooperación entre productores y consumidores.
Referencias:
- Semántica de exactamente una vez y cómo lo hace Apache Kafka
- Transacciones en Apache Kafka
- Habilitar exactamente una vez que Kafka transmita
En un entorno distribuido, la falla es un escenario muy común que puede ocurrir en cualquier momento. En el entorno de Kafka, el corredor puede fallar, fallar en la red, fallar en el procesamiento, fallar al publicar el mensaje o fallar al consumir mensajes, etc. Estos diferentes escenarios introdujeron diferentes tipos de pérdida y duplicación de datos.
Escenarios de falla
A (Ack falló): El productor publicó el mensaje correctamente con reintento> 1, pero no pudo recibir confirmación debido a un error. En ese caso, el productor volverá a intentar el mismo mensaje que podría introducir un duplicado.
B (El proceso del productor falló en los mensajes por lotes): Productor enviando un lote de mensajes falló con pocos éxitos publicados. En ese caso, y una vez que el productor se reinicie, volverá a publicar todos los mensajes del lote, lo que introducirá duplicados en Kafka.
C (Fallo de fuego y olvido) Mensaje publicado por el productor con reintentar = 0 (disparar y olvidar). En caso de falla publicada no se dará cuenta y enviará el siguiente mensaje esto ocasionará que el mensaje se pierda.
D (El consumidor falló en el mensaje por lotes) Un consumidor recibe un lote de mensajes de Kafka y confirma manualmente su desplazamiento (enable.auto.commit = false). Si los consumidores fallaron antes de comprometerse con Kafka, la próxima vez los consumidores volverán a consumir los mismos registros que reproducen duplicados en el lado del consumidor.
Semántica de exactamente una vez
En este caso, incluso si un productor intenta reenviar un mensaje, el mensaje será publicado y consumido por los consumidores exactamente una vez.
Para lograr la semántica Exactamente una vez en Kafka, usa la propiedad inferior a 3
- enable.idempotence = true (dirección a, b & c)
- MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION = 5 (El productor siempre tendrá una solicitud en vuelo por conexión)
- isolated.level = read_committed (dirección d)
Habilitar idempotente (enable.idempotence = true)
La entrega idempotente permite al productor escribir un mensaje a Kafka exactamente una vez en una partición particular de un tema durante la vida de un solo productor sin pérdida de datos y orden por partición.
“Tenga en cuenta que la habilitación de la idempotencia requiere que MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION sea menor o igual a 5, RETRIES_CONFIG sea mayor que 0 y ACKS_CONFIG sea ‘all’. Si estos valores no son establecidos explícitamente por el usuario, se elegirán los valores adecuados. Si los valores incompatibles son establecido, se lanzará una ConfigException “
Para lograr la idempotencia, Kafka utiliza una identificación única que se denomina identificación de producto o PID y número de secuencia mientras produce mensajes. El productor sigue aumentando el número de secuencia en cada mensaje publicado que se asigna con un PID único. El broker siempre compara el número de secuencia actual con el anterior y lo rechaza si el nuevo no es +1 mayor que el anterior lo que evita duplicaciones y al mismo tiempo si es mayor se pierde en mensajes
En un escenario de falla, el corredor comparará los números de secuencia con el anterior y si la secuencia no aumenta +1 rechazará el mensaje.
Transacción (nivel de aislamiento)
Las transacciones nos brindan la capacidad de actualizar datos de manera atómica en múltiples particiones de temas. Todos los registros incluidos en una transacción se guardarán correctamente o ninguno de ellos. Le permite comprometer sus compensaciones de consumidor en la misma transacción junto con los datos que ha procesado, lo que permite una semántica de extremo a extremo exactamente una vez.
El productor no espera para escribir un mensaje a Kafka mientras que el productor usa beginTransaction, commitTransaction y abortTransaction (en caso de falla) El consumidor usa aislamiento.level ya sea read_committed o read_uncommitted
- read_committed: los consumidores siempre leerán solo los datos confirmados.
- read_uncommitted: lee todos los mensajes en orden de compensación sin esperar a que se confirmen las transacciones
Si un consumidor con aislamiento.level = read_committed llega a un mensaje de control para una transacción que no se ha completado, no entregará más mensajes desde esta partición hasta que el productor confirme o anule la transacción o se agote el tiempo de espera de la transacción. El productor determina el tiempo de espera de la transacción utilizando la configuración transaction.timeout.ms (predeterminado 1 minuto).
Exactamente una vez en Producer & Consumer
En condiciones normales donde tenemos productores y consumidores separados. El productor tiene que ser idempotente y, al mismo tiempo, administrar las transacciones para que los consumidores puedan usar aislamiento.level en solo lectura read_committed para hacer que todo el proceso sea una operación atómica. Esto garantiza que el productor siempre se sincronizará con el sistema fuente. Incluso si el productor falla o se cancela una transacción, siempre es consistente y publica un mensaje o lote del mensaje como una unidad una vez.
El mismo consumidor recibirá un mensaje o un lote del mensaje como una unidad una vez.
En Exactamente una vez semántica, Productor junto con Consumidor aparecerán como operación atómica que funcionará como una sola unidad. Publicar y ser consumido de una vez o abortar.
Exactamente una vez en Kafka Stream
Kafka Stream consume mensajes del tema A, procesa y publica un mensaje en el Tema B y, una vez publicado, usa la confirmación (la confirmación se ejecuta principalmente de forma encubierta) para descargar todos los datos de la tienda estatal en el disco.
Exactamente una vez en Kafka Stream hay un patrón de lectura-proceso-escritura que garantiza que esta operación será tratada como una operación atómica. Dado que Kafka Stream abastece al productor, al consumidor y a la transacción, Kafka Stream incluye una garantía de procesamiento de parámetros especial que podría exactamente_una vez o por lo menos_una vez que facilite la vida al no manejar todos los parámetros por separado.
Kafka Streams actualiza automáticamente las compensaciones de los consumidores, las tiendas estatales locales, los temas del registro de cambios de las tiendas estatales y los temas de producción para generar todos juntos. Si alguno de estos pasos falla, todos los cambios se revierten.
Processing.guarantee: exact_once proporciona automáticamente los siguientes parámetros que no es necesario establecer explícitamente
- aislamiento.level = read_committed
- enable.idempotence = true
- MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION = 5