Editor – Esta serie de siete artículos ya está completa:
También puede descargar el conjunto completo de artículos, además de información sobre la implementación de microservicios utilizando NGINX Plus, como un libro electrónico: Microservicios: Desde el diseño hasta la implementación . Y consulte nuestra serie sobre la Arquitectura de referencia de microservicios y la página de Soluciones de microservicios .
Este es el quinto artículo de una serie sobre la creación de aplicações con microservicios. El primer artículo presenta el patrón de arquitectura de microservicios y analiza los beneficios y desventajas de utilizar microservicios. El segundo y tercer artículo de la serie describen diferentes aspectos de la comunicación dentro de una arquitectura de microservicios. El cuarto artículo explora el problema estrechamente relacionado del descubrimiento de servicios. En este artículo, cambiamos de tema y analizamos los problemas de gestión de datos distribuidos que surgen en una arquitectura de microservicios.
Una aplicação monolítica normalmente tiene una única base de datos relacional. Una ventaja clave de usar una base de datos relacional es que su aplicação puede utilizar transacciones ACID , que brindan algunas garantías importantes:
Como resultado, su aplicação puede simplemente comenzar una transacción, cambiar (insertar, actualizar y eliminar) múltiples filas y confirmar la transacción.
Otro gran beneficio de utilizar una base de datos relacional es que proporciona SQL, que es un lenguaje de consulta rico, declarativo y estandarizado. Puede escribir fácilmente una consulta que combine datos de varias tablas. Luego, el planificador de consultas RDBMS determina la forma más óptima de ejecutar la consulta. No tiene que preocuparse por detalles de bajo nivel como cómo acceder a la base de datos. Y, como todos los datos de su aplicación están en una sola base de datos, es fácil realizar consultas.
Desafortunadamente, el acceso a los datos se vuelve mucho más complejo cuando pasamos a una arquitectura de microservicios. Esto se debe a que los datos de cada microservicio son privados y solo se puede acceder a ellos a través de su API. Encapsular los datos garantiza que los microservicios estén acoplados de forma flexible y puedan evolucionar de forma independiente. Si varios servicios acceden a los mismos datos, las actualizaciones del esquema requieren actualizaciones coordinadas y que consumen mucho tiempo para todos los servicios.
Para empeorar las cosas, los diferentes microservicios a menudo utilizan distintos tipos de bases de datos. Las aplicações modernas almacenan y procesan diversos tipos de datos y una base de datos relacional no siempre es la mejor opción. Para algunos casos de uso, una base de datos NoSQL particular podría tener un modelo de datos más conveniente y ofrecer un rendimiento y una escalabilidad mucho mejores. Por ejemplo, tiene sentido que un servicio que almacena y consulta texto utilice un motor de búsqueda de texto como Elasticsearch. De manera similar, un servicio que almacena datos de gráficos sociales probablemente debería utilizar una base de datos de gráficos, como Neo4j. En consecuencia, las aplicações basadas en microservicios a menudo utilizan una combinación de bases de datos SQL y NoSQL, el llamado enfoque de persistencia políglota .
Una arquitectura particionada y persistente políglota para el almacenamiento de datos tiene muchos beneficios, incluidos servicios acoplados de forma flexible y un mejor rendimiento y escalabilidad. Sin embargo, esto presenta algunos desafíos en la gestión de datos distribuidos.
El primer desafío es cómo implementar transacciones comerciales que mantengan la consistencia en múltiples servicios. Para entender por qué esto supone un problema, veamos el ejemplo de una tienda B2B online. El Servicio de Atención al Cliente mantiene información sobre los clientes, incluidas sus líneas de crédito. El Servicio de Pedidos gestiona los pedidos y debe verificar que un nuevo pedido no exceda el límite de crédito del cliente. En la versión monolítica de esta aplicação, el Servicio de Pedidos puede simplemente usar una transacción ACID para verificar el crédito disponible y crear el pedido.
Por el contrario, en una arquitectura de microservicios, las tablas PEDIDO y CLIENTE son privadas para sus respectivos servicios, como se muestra en el siguiente diagrama.
El servicio de pedidos no puede acceder directamente a la tabla CLIENTE. Sólo se puede utilizar la API proporcionada por el Servicio de Atención al Cliente. El Servicio de pedidos podría potencialmente utilizar transacciones distribuidas , también conocidas como confirmación de dos fases (2PC). Sin embargo, 2PC no suele ser una opción viable en las aplicações modernas. El teorema CAP requiere que usted elija entre la disponibilidad y la consistencia estilo ACID, y la disponibilidad suele ser la mejor opción. Además, muchas tecnologías modernas, como la mayoría de las bases de datos NoSQL, no son compatibles con 2PC. Mantener la consistencia de los datos entre servicios y bases de datos es esencial, por lo que necesitamos otra solución.
El segundo desafío es cómo implementar consultas que recuperen datos de múltiples servicios. Por ejemplo, imaginemos que la aplicação necesita mostrar un cliente y sus pedidos recientes. Si el Servicio de pedidos proporciona una API para recuperar los pedidos de un cliente, puede recuperar estos datos mediante una unión del lado de la aplicação. La aplicação recupera al cliente del Servicio de Atención al Cliente y los pedidos del cliente del Servicio de Pedidos. Supongamos, sin embargo, que el Servicio de pedidos solo admite la búsqueda de pedidos por su clave principal (tal vez utiliza una base de datos NoSQL que solo admite recuperaciones basadas en su clave principal). En esta situación, no hay una forma obvia de recuperar los datos necesarios.
Para muchas aplicações, la solución es utilizar una arquitectura basada en eventos . En esta arquitectura, un microservicio publica un evento cuando sucede algo notable, como cuando actualiza una entidad comercial. Otros microservicios se suscriben a esos eventos. Cuando un microservicio recibe un evento, puede actualizar sus propias entidades comerciales, lo que podría generar que se publiquen más eventos.
Puede utilizar eventos para implementar transacciones comerciales que abarquen múltiples servicios. Una transacción consta de una serie de pasos. Cada paso consta de un microservicio que actualiza una entidad comercial y publica un evento que activa el siguiente paso. La siguiente secuencia de diagramas muestra cómo se puede utilizar un enfoque basado en eventos para verificar el crédito disponible al crear un pedido. Los microservicios intercambian eventos a través de un Message Broker.
El servicio de pedidos crea un pedido con estado NUEVO y publica un evento Pedido creado.
El servicio de atención al cliente consume el evento Pedido creado, reserva crédito para el pedido y publica un evento Crédito reservado.
El servicio de pedidos consume el evento Crédito reservado y cambia el estado del pedido a ABIERTO.
Un escenario más complejo podría implicar pasos adicionales, como reservar inventario al mismo tiempo que se verifica el crédito del cliente.
Siempre que (a) cada servicio actualice atómicamente la base de datos y publique un evento (hablaremos más sobre esto más adelante) y (b) el Message Broker garantice que los eventos se entreguen al menos una vez, entonces se pueden implementar transacciones comerciales que abarquen múltiples servicios. Es importante tener en cuenta que estas no son transacciones ACID. Ofrecen garantías mucho más débiles, como la de consistencia final . A este modelo de transacción se le ha denominado modelo BASE .
También puede utilizar eventos para mantener vistas materializadas que unen previamente datos propiedad de múltiples microservicios. El servicio que mantiene la vista se suscribe a los eventos relevantes y actualiza la vista. Por ejemplo, el servicio de actualización de la vista de pedidos de clientes, que mantiene una vista de pedidos de clientes, se suscribe a los eventos publicados por el servicio de atención al cliente y el servicio de pedidos.
Cuando el servicio de actualización de vista de pedidos de clientes recibe un evento de Cliente o Pedido, actualiza el almacén de datos de Vista de pedidos de clientes. Puede implementar la Vista de pedido de cliente utilizando una base de datos de documentos como MongoDB y almacenar un documento para cada cliente. El servicio de consulta de vista de pedidos de clientes maneja las solicitudes de un cliente y pedidos recientes consultando el almacén de datos de vista de pedidos de clientes.
Una arquitectura basada en eventos tiene varias ventajas y desventajas. Permite la implementación de transacciones que abarcan múltiples servicios y proporcionan consistencia eventual. Otro beneficio es que también permite que una aplicação mantenga vistas materializadas. Una desventaja es que el modelo de programación es más complejo que cuando se utilizan transacciones ACID. A menudo es necesario implementar transacciones compensatorias para recuperarse de fallas a nivel de aplicação; por ejemplo, debe cancelar un pedido si falla la verificación de crédito. Además, las aplicações deben lidiar con datos inconsistentes. Esto se debe a que los cambios realizados en las transacciones durante el vuelo son visibles. La aplicação también puede ver inconsistencias si lee desde una vista materializada que aún no está actualizada. Otro inconveniente es que los suscriptores deben detectar e ignorar eventos duplicados.
En una arquitectura basada en eventos también existe el problema de actualizar atómicamente la base de datos y publicar un evento. Por ejemplo, el servicio de pedidos debe insertar una fila en la tabla PEDIDO y publicar un evento Pedido creado. Es esencial que estas dos operaciones se realicen de forma atómica. Si el servicio falla después de actualizar la base de datos pero antes de publicar el evento, el sistema se vuelve inconsistente. La forma estándar de garantizar la atomicidad es utilizar una transacción distribuida que involucre la base de datos y el Message Broker. Sin embargo, por las razones descritas anteriormente, como el teorema CAP, esto es exactamente lo que no queremos hacer.
Una forma de lograr atomicidad es que la aplicação publique eventos utilizando un proceso de varios pasos que involucra solo transacciones locales . El truco es tener una tabla EVENT, que funciona como una cola de mensajes, en la base de datos que almacena el estado de las entidades comerciales. La aplicação inicia una transacción de base de datos (local), actualiza el estado de las entidades comerciales, inserta un evento en la tabla EVENT y confirma la transacción. Un hilo o proceso de aplicação independiente consulta la tabla EVENT, publica los eventos en el Message Broker y luego utiliza una transacción local para marcar los eventos como publicados. El siguiente diagrama muestra el diseño.
El servicio de pedidos inserta una fila en la tabla PEDIDOS e inserta un evento Pedido creado en la tabla EVENTOS. El hilo o proceso Publicador de eventos consulta la tabla EVENT en busca de eventos no publicados, publica los eventos y luego actualiza la tabla EVENT para marcarlos como publicados.
Este enfoque tiene varias ventajas y desventajas. Una ventaja es que garantiza la publicación de un evento para cada actualización sin depender de 2PC. Además, la aplicação publica eventos a nivel empresarial, lo que elimina la necesidad de inferirlos. Una desventaja de este enfoque es que potencialmente es propenso a errores, ya que el desarrollador debe recordar publicar los eventos. Una limitación de este enfoque es que es difícil de implementar cuando se utilizan algunas bases de datos NoSQL debido a sus capacidades limitadas de transacciones y consultas.
Este enfoque elimina la necesidad de 2PC al permitir que la aplicação utilice transacciones locales para actualizar el estado y publicar eventos. Veamos ahora un enfoque que logra atomicidad al hacer que la aplicação simplemente actualice el estado.
Otra forma de lograr atomicidad sin 2PC es que los eventos sean publicados por un hilo o proceso que extrae el registro de transacciones o confirmaciones de la base de datos. La aplicação actualiza la base de datos, lo que genera cambios que se registran en el registro de transacciones de la base de datos. El hilo o proceso Transaction Log Miner lee el registro de transacciones y publica eventos en el Message Broker. El siguiente diagrama muestra el diseño.
Un ejemplo de este enfoque es el proyecto de código abierto LinkedIn Databus . Databus extrae el registro de transacciones de Oracle y publica eventos correspondientes a los cambios. LinkedIn utiliza Databus para mantener varios almacenes de datos derivados consistentes con el sistema de registro.
Otro ejemplo es el mecanismo de flujos en AWS DynamoDB , que es una base de datos NoSQL administrada. Una secuencia de DynamoDB contiene la secuencia ordenada en el tiempo de los cambios (operaciones de creación, actualización y eliminación) realizados en los elementos de una tabla de DynamoDB en las últimas 24 horas. Una aplicação puede leer esos cambios desde la secuencia y, por ejemplo, publicarlos como eventos.
La minería de registros de transacciones tiene varios beneficios y desventajas. Una ventaja es que garantiza la publicación de un evento para cada actualización sin usar 2PC. La minería de registros de transacciones también simplifica la aplicação al separar la publicación de eventos de la lógica de negocio de la aplicación. Una desventaja importante es que el formato del registro de transacciones es propiedad de cada base de datos y puede incluso cambiar entre versiones de bases de datos. Además, puede resultar difícil realizar ingeniería inversa de los eventos comerciales de alto nivel a partir de las actualizaciones de bajo nivel registradas en el registro de transacciones.
La minería de registros de transacciones elimina la necesidad de 2PC al permitir que la aplicação haga una sola cosa: actualizar la base de datos. Veamos ahora un enfoque diferente que elimina las actualizaciones y se basa únicamente en eventos.
El abastecimiento de eventos logra atomicidad sin 2PC al utilizar un enfoque radicalmente diferente y centrado en eventos para persistir entidades comerciales. En lugar de almacenar el estado actual de una entidad, la aplicação almacena una secuencia de eventos que cambian el estado. La aplicação reconstruye el estado actual de una entidad reproduciendo los eventos. Cada vez que cambia el estado de una entidad comercial, se agrega un nuevo evento a la lista de eventos. Dado que guardar un evento es una operación única, es inherentemente atómica.
Para ver cómo funciona el abastecimiento de eventos, considere la entidad Pedido como ejemplo. En un enfoque tradicional, cada pedido se asigna a una fila de una tabla ORDER y a filas de, por ejemplo, una tabla ORDER_LINE_ITEM. Pero cuando se utiliza el abastecimiento de eventos, el Servicio de pedidos almacena un pedido en forma de sus eventos que cambian su estado: Creado, Aprobado, Enviado, Cancelado. Cada evento contiene datos suficientes para reconstruir el estado de la Orden.
Los eventos persisten en un almacén de eventos, que es una base de datos de eventos. La tienda tiene una API para agregar y recuperar eventos de una entidad. El Event Store también se comporta como el Message Broker en las arquitecturas que describimos anteriormente. Proporciona una API que permite a los servicios suscribirse a eventos. Event Store entrega todos los eventos a todos los suscriptores interesados. Event Store es la columna vertebral de una arquitectura de microservicios impulsada por eventos.
La contratación de eventos tiene varios beneficios. Resuelve uno de los problemas clave en la implementación de una arquitectura basada en eventos y permite publicar eventos de manera confiable siempre que el estado cambia. Como resultado, resuelve problemas de consistencia de datos en una arquitectura de microservicios. Además, debido a que persiste eventos en lugar de objetos de dominio, evita en gran medida el problema de desajuste de impedancia relacional de objetos . El abastecimiento de eventos también proporciona un registro de auditoría 100% confiable de los cambios realizados en una entidad comercial y permite implementar consultas temporales que determinan el estado de una entidad en cualquier momento. Otro beneficio importante del abastecimiento de eventos es que su lógica empresarial consta de entidades empresariales débilmente acopladas que intercambian eventos. Esto hace que sea mucho más fácil migrar de una aplicação monolítica a una arquitectura de microservicios.
La contratación de eventos también tiene algunas desventajas. Es un estilo de programación diferente y desconocido, por lo que existe una curva de aprendizaje. El almacén de eventos solo admite directamente la búsqueda de entidades comerciales por clave principal. Debe utilizar la segregación de responsabilidad de consultas de comandos (CQRS) para implementar consultas. Como resultado, las aplicações deben manejar datos consistentes en todo momento.
En una arquitectura de microservicios, cada microservicio tiene su propio almacén de datos privado. Diferentes microservicios pueden utilizar diferentes bases de datos SQL y NoSQL. Si bien esta arquitectura de base de datos tiene beneficios significativos, crea algunos desafíos en la gestión distribuida de datos. El primer desafío es cómo implementar transacciones comerciales que mantengan la consistencia en múltiples servicios. El segundo desafío es cómo implementar consultas que recuperen datos de múltiples servicios.
Para muchas aplicações, la solución es utilizar una arquitectura basada en eventos. Uno de los desafíos que supone implementar una arquitectura basada en eventos es cómo actualizar atómicamente el estado y cómo publicar eventos. Hay algunas formas de lograr esto, entre ellas, el uso de la base de datos como cola de mensajes, la minería de registros de transacciones y el abastecimiento de eventos.
En futuras publicaciones del blog, continuaremos profundizando en otros aspectos de los microservicios.
Editor – Esta serie de siete artículos ya está completa:
También puede descargar el conjunto completo de artículos, además de información sobre la implementación de microservicios utilizando NGINX Plus, como un libro electrónico: Microservicios: Desde el diseño hasta la implementación . Y consulte nuestra serie sobre la Arquitectura de referencia de microservicios y la página de Soluciones de microservicios .
El bloguero invitado Chris Richardson es el fundador del CloudFoundry.com original, una de las primeras PaaS (plataforma como servicio) de Java para Amazon EC2. Ahora asesora a organizaciones para mejorar la forma en que desarrollan e implementan aplicações. También escribe periódicamente sobre microservicios en http://microservices.io .
"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.