Apache Kafka y RabbitMQ: semántica y garantía de entrega de mensajes



Preparamos la traducción de la siguiente parte del artículo en serie, que compara la funcionalidad de Apache Kafka y RabbitMQ. Esta publicación trata sobre semánticas y garantías de entrega de mensajes. Tenga en cuenta que el autor tuvo en cuenta Kafka hasta la versión 0.10 incluida, y apareció exactamente una vez en la versión 0.11. Sin embargo, el artículo sigue siendo relevante y está lleno de puntos útiles desde un punto de vista práctico.
Partes anteriores: primero , segundo .

Tanto RabbitMQ como Kafka ofrecen garantías confiables de entrega de mensajes. Ambas plataformas ofrecen garantías sobre los principios de "como máximo una entrega única" y "al menos una sola vez", pero con el principio de "estrictamente una sola entrega", las garantías de Kafka se aplican de acuerdo con un escenario muy limitado.

Primero, descubramos qué significan estas garantías:

  • Entrega como máximo una vez. Esto significa que el mensaje no se puede entregar más de una vez. Sin embargo, el mensaje puede perderse.
  • Al menos una vez entrega. Esto significa que el mensaje nunca se perderá. En este caso, el mensaje se puede entregar más de una vez.
  • Exactamente una vez entrega. El santo grial de los sistemas de mensajería. Todos los mensajes se entregan estrictamente una vez.

La palabra "entrega" aquí probablemente no sea un término exacto. Sería más exacto decir "procesamiento". En cualquier caso, lo que nos interesa ahora es si el consumidor puede procesar mensajes y según qué principio esto sucede: "no más de uno", "al menos uno" o "estrictamente una vez". Pero la palabra “procesamiento” complica la percepción, y la expresión “entrega por el principio de 'estrictamente una vez'” en este caso no será una definición precisa, porque puede ser necesario entregar el mensaje dos veces para procesarlo una vez correctamente. Si el destinatario se desconectó durante el procesamiento, el mensaje debe enviarse nuevamente al nuevo destinatario.

El segundo Discutiendo el tema del procesamiento de mensajes, llegamos al tema de fallas parciales, lo cual es un dolor de cabeza para los desarrolladores. En el proceso de procesamiento del mensaje hay varias etapas. Consiste en sesiones de comunicación entre la aplicación y el sistema de mensajes al principio y al final, y la aplicación misma trabaja con los datos en el medio. Los escenarios de fallas parciales de la aplicación deben ser manejados por la propia aplicación. Si las operaciones realizadas son completamente transaccionales y los resultados se formulan según el principio de "todo o nada", se pueden evitar fallas parciales en la lógica de la aplicación. Pero a menudo, muchas etapas incluyen la interacción con otros sistemas, donde la transaccionalidad es imposible. Si incluimos en la interacción las relaciones entre los sistemas de mensajería, las aplicaciones, el caché y la base de datos, ¿podemos garantizar el procesamiento "solo una vez"? La respuesta es no.

La estrategia de "estrictamente una vez" se limita a un escenario en el que el único destinatario de los mensajes procesados ​​es la plataforma de mensajería en sí, y esta plataforma proporciona transacciones completas. En este escenario limitado, puede procesar mensajes, escribirlos y enviar señales de que se han procesado como parte de una transacción realizada según el principio de "todo o nada". Es proporcionado por la biblioteca Kafka Streams.

Pero si el procesamiento de mensajes siempre es idempotente, puede evitar la necesidad de implementar la estrategia "estrictamente una vez" a través de las transacciones. Si el procesamiento final de los mensajes es idempotente, puede preocuparse por aceptar duplicados. Pero no todas las acciones pueden implementarse de manera idempotente.

Alerta de extremo a extremo

Lo que no está representado en ningún dispositivo de todos los sistemas de mensajería con los que trabajé es la confirmación de extremo a extremo. Dado que se puede poner en cola un mensaje en RabbitMQ, la notificación de extremo a extremo no tiene sentido. En Kafka, de manera similar, varios grupos diferentes de destinatarios pueden leer información simultáneamente de un tema. En mi experiencia, las alertas de extremo a extremo son lo que a menudo piden las personas que son nuevas en el concepto de mensajería. En tales casos, es mejor explicar de inmediato que esto no es posible.

Cadena de responsabilidad

En general, las fuentes de mensajes no pueden saber que sus mensajes se entregan a los destinatarios. Solo pueden saber que el sistema de mensajería ha recibido sus mensajes y ha asumido la responsabilidad de garantizar su almacenamiento y entrega seguros. Hay una cadena de responsabilidad, que comienza con la fuente, pasa por el sistema de mensajería y termina en el destinatario. Todos deben cumplir correctamente sus deberes y transmitir claramente el mensaje al siguiente. Esto significa que usted, como desarrollador, debe diseñar correctamente sus aplicaciones para evitar la pérdida o el mal uso de los mensajes mientras están bajo su control.

Procedimiento de transferencia de mensajes

Este artículo está dedicado principalmente a cómo cada plataforma proporciona estrategias de envío "al menos una" y "no más de una". Pero todavía hay una orden de mensajería. En las partes anteriores de esta serie, escribí sobre el orden en que se envían los mensajes y el orden en que se procesan, y le aconsejo que consulte estas partes.

En resumen, tanto RabbitMQ como Kafka ofrecen una garantía de primero en entrar, primero en salir (FIFO). RabbitMQ mantiene este orden en el nivel de la cola y Kafka en el nivel de segmentación. Las implicaciones de tales decisiones de diseño se discutieron en artículos anteriores.

Garantías de entrega en RabbitMQ

Se proporcionan garantías de entrega:

  • fiabilidad del mensaje: no desaparecerán mientras estén almacenados en RabbitMQ;
  • notificaciones de mensajes: RabbitMQ intercambia señales con remitentes y destinatarios.

Elementos de confiabilidad


Espejo de cola

Las colas se pueden reflejar (replicar) en muchos nodos (servidores). Cada cola tiene una cola inicial en uno de los nodos. Por ejemplo, hay tres nodos, 10 colas y dos réplicas por cola. Se distribuirán 10 colas de control y 20 réplicas en tres nodos. Se puede configurar la distribución de colas de control por nodos. En caso de congelación de un nodo:

  • en lugar de cada cola inicial en el nodo colgado, se proporciona una réplica de esta cola en otro nodo;
  • Se crean nuevas réplicas en otros nodos para reemplazar las réplicas perdidas en el nodo saliente, lo que admite el factor de replicación.

El tema de la tolerancia a fallas se discutirá en la siguiente parte del artículo.

Colas confiables

Hay dos tipos de colas en RabbitMQ: confiables y poco confiables. Las colas confiables se escriben en el disco y se guardan en caso de reinicio de un nodo. Cuando se inicia el nodo, se anulan.

Mensajes persistentes

Si la cola es confiable, esto no significa que sus mensajes se guardan cuando se reinicia el nodo. Solo se recuperarán los mensajes marcados como persistentes por el remitente.

Cuando trabaje en RabbitMQ, cuanto más confiable sea el mensaje, menor será el rendimiento posible. Si hay una secuencia de eventos en tiempo real y no es crítico perder varios de ellos o un pequeño intervalo de tiempo de la secuencia, es mejor no usar la replicación de cola y transmitir todos los mensajes como inestables. Pero si no es deseable perder mensajes debido a un mal funcionamiento de un nodo, es mejor usar colas confiables con replicación y mensajes estables.

Notificaciones de mensajes


Mensajería

Los mensajes pueden perderse o duplicarse durante la transmisión. Depende del comportamiento del remitente.

"Disparó y olvidó"

La fuente puede decidir no solicitar confirmación del destinatario (notificación de recepción de un mensaje al remitente) y simplemente enviar el mensaje automáticamente. Los mensajes no se duplicarán, pero pueden perderse (lo que satisface la estrategia "como un máximo de entrega única").

Confirmaciones al remitente

Cuando el remitente abre un canal para el intermediario de colas, puede usar el mismo canal para enviar confirmaciones. Ahora, en respuesta al mensaje recibido, el agente de cola debe proporcionar una de dos cosas:

  • básico.ack. Confirmación positiva El mensaje se recibe, la responsabilidad ahora recae en RabbitMQ;
  • básico.nack. Confirmación negativa Algo sucedió y el mensaje no fue procesado. La responsabilidad por ello permanece en la fuente. Si lo desea, puede enviar un mensaje por segunda vez.

Además de las notificaciones de entrega positivas y negativas, se proporciona un mensaje de devolución básico. A veces, el remitente necesita saber no solo que el mensaje llegó a RabbitMQ, sino también que realmente cayó en una o más colas. Puede suceder que la fuente envíe un mensaje al sistema de intercambio de temas, en el que el mensaje no se enruta a ninguna de las colas de entrega. En esta situación, el corredor simplemente descarta el mensaje. En algunos escenarios, esto es normal, en otros, la fuente debe saber si el mensaje ha sido descartado y continuar de acuerdo con esto. Puede establecer el indicador "Obligatorio" para mensajes individuales, y si el mensaje no se ha definido en ninguna cola de entrega, se devolverá basic.return al remitente.

La fuente puede esperar la confirmación después de enviar cada mensaje, pero esto reducirá en gran medida su rendimiento. En cambio, las fuentes pueden enviar un flujo constante de mensajes, estableciendo un límite en la cantidad de mensajes no reconocidos. Cuando se alcanza el límite de mensajes provisionales, el envío se detendrá hasta que se reciban todas las confirmaciones.

Ahora que hay muchos mensajes en tránsito desde el remitente a RabbitMQ, las confirmaciones se agrupan utilizando el indicador múltiple para mejorar el rendimiento. A todos los mensajes enviados a través del canal se les asigna un valor entero que aumenta monotónicamente, el "Número de secuencia". La notificación de un mensaje incluye el número de secuencia del mensaje correspondiente. Y si al mismo tiempo el valor es multiple = true, el remitente debe rastrear los números de secuencia de sus mensajes para saber qué mensajes se entregaron con éxito y cuáles no. Escribí un artículo detallado sobre este tema.

Gracias a las confirmaciones, evitamos la pérdida de mensajes de las siguientes maneras:

  • reenviar mensajes en caso de una notificación negativa;
  • almacenamiento continuo de mensajes en algún lugar en caso de notificación negativa o devolución básica.

Transacciones

Las transacciones rara vez se usan en RabbitMQ por los siguientes motivos:

  • Garantías débiles. Si los mensajes se enrutan a varias colas o tienen un icono obligatorio, no se admitirá la continuidad de la transacción;
  • Baja productividad.

Honestamente, nunca los usé, no dan ninguna garantía adicional, excepto las confirmaciones al remitente, y solo aumentan la incertidumbre en la cuestión de cómo interpretar el acuse de recibo de los mensajes derivados de la finalización de las transacciones.

Errores de comunicación / canal

Además de las notificaciones de recepción de mensajes, el remitente debe tener en cuenta las fallas de las herramientas de comunicación y los corredores. Ambos factores conducen a la pérdida del canal de comunicación. Con la pérdida de canales, la oportunidad de recibir las notificaciones de recepción de mensajes aún no recibidos desaparece. Aquí, el remitente debe elegir entre el riesgo de pérdida de mensajes y el riesgo de duplicación.

La falla del intermediario puede ocurrir cuando el mensaje estaba en el búfer del sistema operativo o preprocesado, y luego el mensaje se perderá. O tal vez el mensaje estaba en cola, pero el agente de mensajes murió antes de enviar una confirmación. En este caso, el mensaje se entregará con éxito.

Del mismo modo, el fracaso de los medios de comunicación afecta la situación. ¿Ocurrió una falla durante la transmisión del mensaje? ¿O después de que el mensaje haya sido puesto en cola, pero antes de recibir una notificación positiva?

El remitente no puede determinar esto, por lo que debe elegir una de las siguientes opciones:

  • No reenvíe el mensaje, creando un riesgo de pérdida;
  • reenvíe el mensaje y cree un riesgo de duplicación.

Si hay muchos mensajes del remitente en tránsito, el problema se vuelve más complicado. Lo único que puede hacer el remitente es dar una pista a los destinatarios agregando un encabezado especial al mensaje, lo que indica que el mensaje se está enviando por segunda vez. Los destinatarios pueden decidir verificar la presencia de dichos encabezados en los mensajes y, si se encuentran, verificar si los mensajes recibidos están duplicados (si dicha verificación no se ha realizado previamente).

Destinatarios


Hay dos opciones disponibles para los destinatarios para recibir notificaciones:

  • sin modo de notificación;
  • modo de notificación manual.

Sin modo de notificación

Es un modo de notificaciones automáticas. Y él es peligroso. En primer lugar, porque cuando un mensaje ingresa a su aplicación, se elimina de la cola. Esto puede conducir a la pérdida de mensajes si:

  • la conexión se interrumpió antes de recibir el mensaje;
  • el mensaje todavía está en el búfer interno y la aplicación se deshabilitó;
  • el procesamiento del mensaje falló.

Además, estamos perdiendo mecanismos de contrapresión como un medio para monitorear la calidad de la entrega de mensajes. Al configurar el modo para enviar notificaciones manualmente, puede establecer una captación previa (o establecer el nivel de servicios proporcionados, QoS) para limitar el número de mensajes únicos que el sistema aún no ha confirmado. Sin esto, RabbitMQ envía mensajes tan rápido como lo permite la conexión, y esto puede ser más rápido de lo que el receptor puede procesarlos. Como resultado, los búferes están llenos y se producen errores de memoria.

Modo de notificación manual

El destinatario debe enviar manualmente una notificación de recepción de cada mensaje. Puede establecer una captación previa en caso de que el número de mensajes sea más de uno, y procesar muchos mensajes al mismo tiempo. Puede decidir enviar una notificación para cada mensaje, o puede aplicar la bandera múltiple y enviar varias notificaciones a la vez. Las notificaciones de agrupación mejoran el rendimiento.

Cuando el destinatario abre el canal, los mensajes que lo atraviesan contienen el parámetro Etiqueta de entrega, cuyo valor es un número entero, que aumenta monotónicamente. Se incluye en cada notificación de recibo y se utiliza como identificador de mensaje.

Las notificaciones pueden ser las siguientes:

  • básico.ack. Después de eso, RabbitMQ elimina el mensaje de la cola. La bandera múltiple se puede aplicar aquí.
  • básico.nack. El destinatario debe establecer una bandera para indicarle a RabbitMQ si debe volver a poner el mensaje en cola. Al volver a configurar el mensaje va al comienzo de la cola. Desde allí se envía nuevamente al destinatario (incluso al mismo destinatario). La notificación basic.nack admite el indicador múltiple.
  • rechazo basico. Igual que basic.nack, pero no admite el indicador múltiple.

Por lo tanto, semánticamente basic.ack y basic.nack con requeue = false son lo mismo. Ambos operadores significan eliminar un mensaje de la cola.

La siguiente pregunta es cuándo enviar notificaciones de recibo. Si el mensaje se procesó rápidamente, es posible que desee enviar una notificación inmediatamente después de completar esta operación (correcta o incorrecta). Pero si el mensaje estaba en la cola RabbitMQ y el procesamiento lleva muchos minutos. Enviar una notificación después de esto será problemático, porque si el canal se cierra, todos los mensajes para los que no hubo notificaciones serán devueltos a la cola y el envío se realizará por segunda vez.

Error de conexión / intermediario de mensajes

Si se desconectó la conexión o se produjo un error en el intermediario, después de lo cual el canal deja de funcionar, todos los mensajes cuyo recibo no ha sido confirmado se vuelven a poner en cola y se reenvían. Esto es bueno porque evita la pérdida de datos, pero malo porque causa una duplicación excesiva.

Cuanto más tiempo tenga el destinatario mensajes durante mucho tiempo, cuyo recibo no haya confirmado, mayor será el riesgo de reenvío. Cuando se reenvía un mensaje, RabbitMQ para el indicador de reenvío se establece en verdadero. Debido a esto, el destinatario al menos tiene una indicación de que el mensaje puede haber sido procesado.

Idempotencia

Si se requiere idempotencia y garantiza que no se pierda ningún mensaje, se deben incorporar algunos cheques duplicados u otros esquemas idempotentes. Si buscar mensajes duplicados es demasiado costoso, puede aplicar una estrategia en la que el remitente siempre agrega un encabezado especial a los mensajes reenviados, y el destinatario verifica la presencia de dicho encabezado y una bandera de reenvío en los mensajes recibidos.

Conclusión


RabbitMQ ofrece garantías confiables de mensajería a largo plazo, pero hay muchas situaciones en las que no ayudarán.

Aquí hay una lista de puntos para recordar:

  • Debe usar la duplicación de colas, las colas confiables, los mensajes persistentes, los reconocimientos para el remitente, un indicador de confirmación y la notificación forzada del destinatario si se requieren garantías confiables en la estrategia de "al menos una vez de entrega".
  • Si el envío se lleva a cabo como parte de la estrategia de "al menos una entrega", es posible que deba agregar un mecanismo de deduplicación o idempotencia al duplicar los datos que se envían.
  • Si la cuestión de la pérdida de mensajes no es tan importante como la cuestión de la velocidad de entrega y la alta escalabilidad, piense en sistemas sin redundancia, sin mensajes persistentes y sin reconocimiento en el lado de la fuente. Sin embargo, preferiría dejar notificaciones forzadas del destinatario para controlar el flujo de mensajes recibidos cambiando las restricciones de captación previa. En este caso, deberá enviar notificaciones en lotes y utilizar la bandera "múltiple".

Garantías de entrega en Kafka

Se proporcionan garantías de entrega:

  • durabilidad del mensaje: los mensajes almacenados en un segmento no se pierden;
  • Notificaciones de mensajes: el intercambio de señales entre Kafka (y posiblemente el repositorio de Apache Zookeeper) por un lado y la fuente / receptor por el otro.

Dos palabras sobre el empaquetado de mensajes

Una de las diferencias entre RabbitMQ y Kafka es el uso de paquetes para mensajería.

RabbitMQ proporciona algo similar al embalaje gracias a:

  • Suspenda el envío de cada mensaje X hasta que se reciban todas las notificaciones. RabbitMQ generalmente agrupa las notificaciones utilizando el indicador "múltiple".
  • «prefetch» «multiple».

. “multiple”. TCP.

Kafka . , . RabbitMQ, , , . , .

Kafka , , . , . RabbitMQ API , . RabbitMQ .

,



Kafka - , , . . , , , , , .

Kafka (In Sync Replicas, ISR). . , , ( 10 ). , . - , .. . .



, Kafka , , , Kafka .



, Kafka, , :

  • , . Acks=0.
  • . Acks=1
  • . Acks=All

, RabbitMQ. , , ( , ). , .

Kafka . :

  • enable.idempotence “true”,
  • max.in.flight.requests.per.connection 5 ,
  • retries 1 ,
  • acks “all”.

, acks=0/1 , .



, , . ZooKeeper Kafka.

(), , :

  • . . . . , .
  • , . “ ”. , ; , . , 10 , 4 , , , ;
  • , . “ ”. , , , . , 10 , , 4 ;
  • . , .

“ ” Kafka Streams, Java. Java . “ ”, , , . , , “ ” . , , () .

, Kafka Streams, , , “ ”. Kafka: . , . , , , ( ), .



Kafka “--”. . , , .

“ ”, , (, , ). “ ”, , . .

: “ ” ? . , , . . (Last Stable Offset, LSO) — ; “ ” .

Conclusiones

. , , . , Kafka , .

Para resumir


  • “ ” “ ”.
  • .
  • , . Kafka , .
  • , , .
  • .
  • Kafka , “--”. .
  • Kafka, - , ( ). RabbitMQ .
  • Kafka , RabbitMQ , .

Source: https://habr.com/ru/post/437446/


All Articles