Arquitectura basada en eventos con Pub/Sub

En este documento se analizan las diferencias entre las arquitecturas basadas en colas de mensajes locales y las arquitecturas basadas en eventos en la nube que se implementan en Pub/Sub. Si intentas aplicar patrones on-premise directamente a tecnologías basadas en la nube, puedes perderte el valor único que hace que la nube sea atractiva.

Este documento está dirigido a arquitectos de sistemas que están migrando diseños de arquitecturas on-premise a diseños basados en la nube. En este documento se presupone que tienes conocimientos básicos sobre los sistemas de mensajería.

En el siguiente diagrama se muestra un resumen de un modelo de cola de mensajes y un modelo de publicación/suscripción.

Compara la arquitectura de un modelo de cola de mensajes con un modelo basado en eventos mediante Pub/Sub.

En el diagrama anterior, se compara un modelo de cola de mensajes con un modelo de flujo de eventos de Pub/Sub. En un modelo de cola de mensajes, el editor envía mensajes a una cola en la que cada suscriptor puede escuchar una cola concreta. En el modelo de flujo de eventos que usa Pub/Sub, el editor envía mensajes a un tema que pueden escuchar varios suscriptores. Las diferencias entre estos modelos se describen en las siguientes secciones.

Comparación entre flujos de eventos y mensajería basada en colas

Si trabajas con sistemas locales, ya conoces los buses de servicios empresariales (ESBs) y las colas de mensajes. Los flujos de eventos son un nuevo patrón y tienen diferencias importantes con ventajas concretas para los sistemas modernos en tiempo real.

En este documento se describen las principales diferencias en el mecanismo de transporte y en los datos de la carga útil en la arquitectura basada en eventos.

Transporte de mensajes

Los sistemas que mueven datos en estos modelos se denominan "brokers de mensajes" y hay varios frameworks implementados en ellos. Uno de los primeros conceptos es el mecanismo subyacente que transporta los mensajes del editor al receptor. En los frameworks de mensajería locales, el sistema de origen emite un mensaje explícito, remoto y desacoplado a un sistema de procesamiento de nivel inferior mediante una cola de mensajes como transporte.

En el siguiente diagrama se muestra un modelo de cola de mensajes:

Los mensajes de un editor se envían a una cola única para cada suscriptor.

En el diagrama anterior, los mensajes fluyen de un proceso de editor upstream a un proceso de suscriptor downstream mediante una cola de mensajes.

El sistema A (el editor) envía un mensaje a una cola del intermediario de mensajes designada para el sistema B (el suscriptor). Aunque el suscriptor de la cola puede estar formado por varios clientes, todos esos clientes son instancias duplicadas del sistema B que se han implementado para mejorar la escalabilidad y la disponibilidad. Si otros procesos posteriores, como el sistema C, necesitan consumir los mismos mensajes del productor (sistema A), se requiere una nueva cola. Debes actualizar el productor para que publique los mensajes en la nueva cola. Este modelo se suele denominar transmisión de mensajes.

La capa de transporte de mensajes de estas colas puede o no proporcionar garantías de orden de los mensajes. Por lo general, se espera que las colas de mensajes proporcionen un modelo con orden garantizado, en el que los datos se secuencian según un modelo de acceso estricto de tipo FIFO (primero en entrar, primero en salir), similar a una cola de tareas. Este patrón es fácil de implementar al principio, pero, con el tiempo, presenta problemas de escalado y operativos. Para implementar mensajes ordenados, el sistema necesita un proceso central para organizar los datos. Este proceso limita las funciones de escalado y reduce la disponibilidad del servicio, ya que es un punto único de fallo.

Los brokers de mensajería de estas arquitecturas suelen implementar lógica adicional, como hacer un seguimiento de qué suscriptor ha recibido qué mensajes y monitorizar la carga de los suscriptores. Los suscriptores suelen ser meramente reactivos, no tienen ningún conocimiento del sistema en general y simplemente ejecutan una función al recibir un mensaje. Este tipo de arquitecturas se denominan tuberías inteligentes (sistema de colas de mensajes) y endpoints tontos (suscriptor).

Transporte de Pub/Sub

Al igual que los sistemas orientados a mensajes, los sistemas de streaming de eventos también transportan mensajes de un sistema de origen a sistemas de destino desacoplados. Sin embargo, en lugar de enviar cada mensaje a una cola específica de un proceso, los sistemas basados en eventos suelen publicar mensajes en un tema compartido y, a continuación, uno o varios receptores se suscriben a ese tema para recibir los mensajes relevantes.

En el siguiente diagrama se muestra cómo emite un editor upstream varios mensajes a un solo tema y, a continuación, se dirigen al suscriptor downstream correspondiente:

Los mensajes de un editor se envían a un solo tema para todos los suscriptores.

Este patrón de publicación y suscripción es el origen del término pub/sub. Este patrón también es la base del producto Google Cloud llamado Pub/Sub. En este documento, "pub/sub" hace referencia al patrón y "Pub/Sub" al producto.

En el modelo de publicación y suscripción, el sistema de mensajería no necesita conocer a ninguno de los suscriptores. No registra qué mensajes se han recibido ni gestiona la carga del proceso de consumo. En su lugar, los suscriptores monitorizan qué mensajes se han recibido y son responsables de autogestionar los niveles de carga y el escalado.

Una de las ventajas más importantes es que, a medida que descubras nuevos usos para los datos del modelo de publicación y suscripción, no tendrás que actualizar el sistema de origen para publicar en nuevas colas ni duplicar datos. En su lugar, adjunta el nuevo consumidor a una nueva suscripción sin que esto afecte al sistema actual.

Las llamadas en los sistemas de transmisión de eventos casi siempre son asíncronas, envían eventos y no esperan ninguna respuesta. Los eventos asíncronos permiten más opciones de escalado tanto para el productor como para los consumidores. Sin embargo, este patrón asíncrono puede plantear problemas si esperas que los mensajes se envíen en orden FIFO.

Datos de la cola de mensajes

Los datos que se transfieren entre sistemas de colas de mensajes y sistemas basados en publicación-suscripción se suelen denominar mensajes en ambos contextos. Sin embargo, el modelo en el que se presentan esos datos es diferente. En los sistemas de colas de mensajes, los mensajes reflejan un comando que tiene como objetivo cambiar el estado de los datos de nivel inferior. Si observas los datos de los sistemas de colas de mensajes locales, el editor puede indicar explícitamente lo que debe hacer el consumidor. Por ejemplo, un mensaje de inventario puede indicar lo siguiente:

<m:SetInventoryLevel>
    <inventoryValue>3001</inventoryValue>
</m: SetInventoryLevel>

En este ejemplo, el productor le indica al consumidor que debe definir el nivel de inventario en 3001. Este enfoque puede ser complicado porque el productor debe entender la lógica empresarial de cada consumidor y crear estructuras de mensajes independientes para diferentes casos prácticos. Este sistema de colas de mensajes era una práctica habitual en los grandes monolitos que implementaban la mayoría de las empresas. Sin embargo, si quieres avanzar más rápido, ampliar la escala e innovar más que antes, estos sistemas centralizados pueden convertirse en un cuello de botella, ya que los cambios son lentos y arriesgados.

Este patrón también presenta problemas operativos. Cuando se producen datos incorrectos, registros duplicados u otros problemas que deben corregirse, este modelo de mensajería supone un reto importante. Por ejemplo, si necesitas deshacer el mensaje usado en el ejemplo anterior, no sabrás qué valor corregido debes definir porque no tienes ninguna referencia al estado anterior. No tienes información sobre si el valor del inventario era de 3000 o 4000 antes de que se enviara ese mensaje.

Datos de Pub/Sub

Los eventos son otra forma de enviar datos de mensajes. Lo que los diferencia es que los sistemas basados en eventos se centran en el evento que se ha producido en lugar del resultado que debería producirse. En lugar de enviar datos que indiquen qué acción debe llevar a cabo un consumidor, los datos se centran en los detalles del evento real producido. Puedes implementar sistemas basados en eventos en varias plataformas, pero se suelen usar en sistemas basados en publicación y suscripción.

Por ejemplo, un evento de inventario podría tener el siguiente aspecto:

{ "inventory":-1 }

Los datos del evento anterior indican que se ha producido un evento que ha reducido el inventario en 1. Los mensajes se centran en el evento que ha ocurrido en el pasado y no en un estado que se vaya a cambiar en el futuro. Los editores pueden enviar mensajes de forma asíncrona, lo que hace que los sistemas basados en eventos sean más fáciles de escalar que los modelos de colas de mensajes. En el modelo de publicación y suscripción, puedes desacoplar la lógica empresarial para que el productor solo tenga que entender las acciones que se realizan en él y no necesite comprender los procesos posteriores. Los suscriptores de esos datos pueden elegir la mejor forma de gestionarlos. Como estos mensajes no son comandos imperativos, el orden de los mensajes es menos importante.

Con este patrón, es más fácil revertir los cambios. En este ejemplo, no se necesita información adicional porque puedes negar el valor del inventario para moverlo en la dirección opuesta. Ya no tendrás que preocuparte por los mensajes que lleguen tarde o en orden incorrecto.

Comparación de modelos

En este caso, tiene cuatro unidades del mismo producto en su inventario. Un cliente devuelve una unidad del producto y el siguiente compra tres unidades del mismo producto. En este caso, supongamos que el mensaje del producto devuelto se ha retrasado.

En la siguiente tabla se compara el nivel de inventario del modelo de cola de mensajes que recibe el recuento de inventario en el orden correcto con el mismo modelo que recibe el recuento de inventario en un orden incorrecto:

Cola de mensajes (orden correcto) Cola de mensajes (desordenada)
Inventario inicial: 4 Inventario inicial: 4
Mensaje 1: setInventory(5) Mensaje 2: setInventory(2)
Mensaje 2: setInventory(2) Mensaje 1: setInventory(5)
Nivel de inventario: 2 Nivel de inventario: 5

En el modelo de cola de mensajes, el orden en el que se reciben los mensajes es importante porque el mensaje contiene el valor precalculado. En este ejemplo, si los mensajes llegan en el orden correcto, el nivel de inventario es 2. Sin embargo, si los mensajes llegan en orden incorrecto, el nivel de inventario es 5, lo que es incorrecto.

En la siguiente tabla se compara el nivel de inventario del sistema basado en Pub/Sub que recibe el recuento de inventario en el orden correcto con el mismo sistema que recibe el recuento de inventario en un orden incorrecto:

Pubsub (orden correcto) Pubsub (fuera de servicio)
Inventario inicial: 4 Inventario inicial: 4
Mensaje 2: "inventory":-3 Mensaje 1: "inventory":+1
Mensaje 1: "inventory":+1 Mensaje 2: "inventory":-3
Nivel de inventario: 2 Nivel de inventario: 2

En el sistema basado en pub/sub, el orden de los mensajes no importa porque se basa en los servicios que producen eventos. Independientemente del orden en el que lleguen los mensajes, el nivel de inventario es preciso.

En el siguiente diagrama se muestra cómo, en el modelo de cola de mensajes, la cola ejecuta comandos que indican al suscriptor cómo debe cambiar el estado, mientras que, en el modelo de publicación y suscripción, los suscriptores reaccionan a los datos de eventos que indican lo que ha ocurrido en el editor:

Ejemplo de compra que compara la reacción a comandos con la reacción a eventos.

Implementar arquitecturas basadas en eventos

Hay varios conceptos que debes tener en cuenta al implementar arquitecturas basadas en eventos. En las siguientes secciones se presentan algunos de esos temas.

Garantías de entrega

Un concepto que surge en una conversación sobre sistemas es la fiabilidad de las garantías de entrega de mensajes. Es posible que diferentes proveedores y sistemas ofrezcan distintos niveles de fiabilidad, por lo que es importante conocer las variaciones.

El primer tipo de garantía plantea una pregunta sencilla: si se envía un mensaje, ¿se garantiza que se entregará? Esto es lo que se conoce como entrega al menos una vez. Se garantiza que el mensaje se entregará al menos una vez, pero es posible que se envíe más de una vez.

Otro tipo de garantía es la de entrega como máximo una vez. Con la entrega "como mucho una vez", el mensaje solo se entrega una vez como máximo, pero no hay garantías de que se entregue.

La última variación de las garantías de entrega es la entrega exactamente una vez. En este modelo, el sistema envía una y solo una copia del mensaje, que se entrega con garantía.

Orden y duplicados

En las arquitecturas locales, los mensajes suelen seguir un modelo FIFO. Para conseguir este modelo, un sistema de procesamiento centralizado gestiona la secuencia de los mensajes para asegurar que se ordenen correctamente. La mensajería ordenada plantea problemas, ya que, si falla un mensaje, todos los mensajes deben volver a enviarse en secuencia. Cualquier sistema centralizado puede suponer un problema para la disponibilidad y la escalabilidad. Para escalar un sistema central que gestiona los pedidos, normalmente solo se pueden añadir más recursos a una máquina. Si un solo sistema gestiona el pedido, cualquier problema de fiabilidad afectará a todo el sistema, no solo a esa máquina.

Los servicios de mensajería de alta escalabilidad y disponibilidad suelen usar varios sistemas de procesamiento para asegurarse de que los mensajes se entregan al menos una vez. En muchos sistemas, no se puede garantizar la gestión del orden de los mensajes.

Las arquitecturas basadas en eventos no dependen del orden de los mensajes y pueden tolerar mensajes duplicados. Si se requiere un orden, los subsistemas pueden implementar técnicas de agregación y de ventanas. Sin embargo, este enfoque sacrifica la escalabilidad y la disponibilidad en ese componente.

Técnicas de filtrado y difusión

Como un flujo de eventos puede contener datos que un suscriptor puede necesitar o no, a menudo es necesario limitar los datos que recibe un suscriptor concreto. Hay dos patrones para gestionar este requisito: filtros de eventos y distribuciones de eventos.

En el siguiente diagrama se muestra un sistema basado en eventos con filtros de eventos que filtran mensajes para los suscriptores:

Modelo basado en eventos con un filtro de eventos que filtra los mensajes para los suscriptores.

En el diagrama anterior, los filtros de eventos usan mecanismos de filtrado que limitan los eventos que llegan al suscriptor. En este modelo, un solo tema contiene todas las variaciones de un mensaje. En lugar de que un suscriptor lea cada mensaje y verifique si es aplicable, la lógica de filtrado del sistema de mensajería evalúa el mensaje y lo oculta a los demás suscriptores.

En el siguiente diagrama se muestra una variación del patrón de filtro de eventos llamada "distribución de eventos" que usa varios temas:

Modelo basado en eventos con distribución de eventos que vuelve a publicar mensajes en temas.

En el diagrama anterior, el tema principal contiene todas las variaciones de un mensaje, pero un mecanismo de distribución de eventos vuelve a publicar los mensajes en temas relacionados con ese subconjunto de suscriptores.

Colas de mensajes sin procesar

Incluso en los mejores sistemas, pueden producirse fallos. Las colas de mensajes no procesados son una técnica para gestionar estos fallos. En la mayoría de las arquitecturas basadas en eventos, el sistema de mensajes sigue proporcionando un mensaje a un suscriptor hasta que este lo confirma.

Si hay un problema con un mensaje (por ejemplo, caracteres no válidos en el cuerpo del mensaje), es posible que el suscriptor no pueda confirmar la recepción del mensaje. El sistema puede no gestionar la situación o incluso finalizar el proceso.

Los sistemas suelen volver a intentar enviar los mensajes que no se han confirmado o que han dado error. Si no se confirma un mensaje no válido después de un periodo de tiempo predeterminado, el mensaje acabará agotando el tiempo de espera y se eliminará del tema. Desde el punto de vista operativo, es útil revisar los mensajes en lugar de que desaparezcan. Es ahí donde entran en juego las colas de mensajes sin procesar. En lugar de eliminar el mensaje del tema, se mueve a otro tema donde se puede volver a procesar o revisar para entender por qué se ha producido un error.

Historial de emisiones y repeticiones

Los flujos de eventos son flujos de datos continuos. Acceder a este historial de datos es útil. Puede que quieras saber cómo ha llegado un sistema a un estado determinado. Puede que tengas preguntas relacionadas con la seguridad que requieran una auditoría de los datos. Poder registrar un historial de los eventos es fundamental para las operaciones a largo plazo de un sistema basado en eventos.

Un uso habitual de los datos de eventos históricos es utilizarlos con un sistema de repetición. Las repeticiones se usan con fines de prueba. Al reproducir datos de eventos de producción en otros entornos, como los de pruebas y de preproducción, puede validar nuevas funciones con conjuntos de datos reales. También puedes reproducir el historial de datos para recuperarte de un estado fallido. Si un sistema falla o pierde datos, los equipos pueden reproducir el historial de eventos desde un punto correcto conocido y el servicio puede reconstruir el estado que ha perdido.

Registrar estos eventos en colas basadas en registros o en flujos de registro también es útil cuando los suscriptores necesitan acceder a una secuencia de eventos en momentos diferentes. Los flujos de registro se pueden ver en sistemas con funciones sin conexión. Si usas el historial de tu flujo, puedes procesar las nuevas entradas más recientes leyendo el flujo a partir del puntero last-read.

Vistas de datos: en tiempo real y casi en tiempo real

Como todos los datos fluyen a través de los sistemas, es importante que puedas usarlos. Hay muchas técnicas para acceder a estos flujos de eventos y usarlos, pero un caso práctico habitual es conocer el estado general de los datos en un momento concreto. A menudo, se trata de preguntas orientadas a cálculos, como "cuántos" o "nivel actual", que pueden usar otros sistemas o los usuarios. Hay varias implementaciones que pueden responder a estas preguntas:

  • Un sistema en tiempo real puede ejecutarse de forma continua y hacer un seguimiento del estado actual. Sin embargo, como el sistema solo tiene un cálculo en memoria, cualquier tiempo de inactividad hace que el cálculo sea cero.
  • El sistema puede calcular los valores de la tabla de historial de cada solicitud, pero esto puede convertirse en un problema, ya que intentar calcular los valores de cada solicitud a medida que crecen los datos puede resultar inviable.
  • El sistema puede crear instantáneas de los cálculos a intervalos específicos, pero si solo se usan las instantáneas, no se reflejan los datos en tiempo real.

Un patrón útil para implementar es una arquitectura Lambda con funciones casi en tiempo real y en tiempo real. Por ejemplo, una página de producto de un sitio de comercio electrónico puede usar vistas casi en tiempo real de los datos de inventario. Cuando los clientes hacen pedidos, se usa un servicio en tiempo real para asegurarse de que los datos de inventario se actualizan al segundo. Para implementar este patrón, el servicio responde a las solicitudes casi en tiempo real de una tabla de instantáneas que contiene valores calculados en un intervalo determinado. Una solicitud en tiempo real usa tanto la tabla de la instantánea como los valores de la tabla de historial desde la última instantánea para obtener el estado actual exacto. Estas vistas materializadas de los flujos de eventos proporcionan datos útiles para impulsar procesos empresariales reales.

Siguientes pasos