Compartir a través de


Desafíos y soluciones para la administración de datos distribuidos

Sugerencia

Este contenido es un extracto del libro electrónico, ".NET Microservices Architecture for Containerized .NET Applications" (Arquitectura de microservicios de .NET para aplicaciones de .NET contenedorizadas), disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.

Miniatura de la portada del libro electrónico 'Arquitectura de microservicios de .NET para aplicaciones .NET contenedorizadas'.

Desafío 1: Cómo definir los límites de cada microservicio

Definir límites de microservicios probablemente es el primer desafío que cualquier persona encuentra. Cada microservicio debe ser una parte de la aplicación y cada microservicio debe ser autónomo con todas las ventajas y desafíos que transmite. ¿Pero cómo se identifican esos límites?

En primer lugar, debe centrarse en los modelos de dominio lógicos de la aplicación y en los datos relacionados. Intente identificar islas desacopladas de datos y contextos diferentes dentro de la misma aplicación. Cada contexto podría tener un lenguaje de negocio diferente (términos empresariales diferentes). Los contextos deben definirse y administrarse de forma independiente. Los términos y entidades que se usan en esos contextos diferentes pueden parecer similares, pero es posible que descubra que, en un contexto determinado, se usa un concepto empresarial con uno para un propósito diferente en otro contexto e incluso podría tener un nombre diferente. Por ejemplo, un usuario puede denominarse usuario en el contexto de identidad o pertenencia, como cliente en un contexto de CRM, como comprador en un contexto de ordenación, etc.

La manera de identificar los límites entre varios contextos de aplicación con un dominio diferente para cada contexto es exactamente cómo puede identificar los límites de cada microservicio empresarial y su modelo de dominio y datos relacionados. Siempre intenta minimizar el acoplamiento entre esos microservicios. En esta guía se detalla más detalladamente este diseño de modelo de dominio y identificación en la sección Identificación de los límites del modelo de dominio para cada microservicio más adelante.

Desafío 2: Creación de consultas que recuperan datos de varios microservicios

Un segundo desafío es cómo implementar consultas que recuperan datos de varios microservicios, al tiempo que evitan la comunicación chatty con los microservicios desde aplicaciones cliente remotas. Un ejemplo podría ser una sola pantalla de una aplicación móvil que necesita mostrar la información del usuario que pertenece a los microservicios de cesta, catálogo e identidad de usuario. Otro ejemplo sería un informe complejo que implicaba muchas tablas ubicadas en varios microservicios. La solución adecuada depende de la complejidad de las consultas. Pero en cualquier caso, necesitará una manera de agregar información si desea mejorar la eficacia en las comunicaciones del sistema. Las soluciones más populares son las siguientes.

Puerta de enlace de API. Para la agregación de datos sencilla de varios microservicios que poseen bases de datos diferentes, el enfoque recomendado es un microservicio de agregación al que se hace referencia como puerta de enlace de API. Sin embargo, debe tener cuidado de implementar este patrón, ya que puede ser un punto de ahogo en el sistema, y puede infringir el principio de autonomía de microservicios. Para mitigar esta posibilidad, puede tener varias puertas de enlace de API específicas definidas, cada una de ellas centrada en un "segmento" vertical o un área de negocio del sistema. El patrón de puerta de enlace de API se explica con más detalle en la sección de puerta de enlace de API más adelante.

Federación de GraphQL Una opción que se debe tener en cuenta si los microservicios ya usan GraphQL es La federación de GraphQL. La federación permite definir "subgráficos" de otros servicios y redactarlos en un "supergráfico" agregado que actúa como un esquema independiente.

CQRS con tablas de consulta o lectura. Otra solución para agregar datos de varios microservicios es el patrón Materialized View. En este enfoque, se genera de antemano (preparar datos desnormalizados antes de que se produzcan las consultas reales), una tabla de solo lectura con los datos que pertenecen a varios microservicios. La tabla tiene un formato adecuado para las necesidades de la aplicación cliente.

Considere algo parecido a la pantalla de una aplicación móvil. Si tiene una base de datos única, puede extraer los datos de esa pantalla mediante una consulta SQL que realice una combinación compleja que implique varias tablas. Sin embargo, cuando tiene varias bases de datos y cada base de datos es propiedad de un microservicio diferente, no puede consultar esas bases de datos y crear una combinación SQL. La consulta compleja se convierte en un desafío. Puede abordar el requisito mediante un enfoque de CQRS: se crea una tabla desnormalizada en una base de datos diferente que se usa solo para las consultas. La tabla se puede diseñar específicamente para los datos que necesita para la consulta compleja, con una relación uno a uno entre los campos necesarios para la pantalla de la aplicación y las columnas de la tabla de consulta. También podría servir para la elaboración de informes.

Este enfoque no solo resuelve el problema original (cómo consultar y combinar entre microservicios), sino que también mejora considerablemente el rendimiento en comparación con una combinación compleja, ya que ya tiene los datos que la aplicación necesita en la tabla de consulta. Por supuesto, la utilización de CQRS (Segregación de responsabilidades de comandos y consultas) con tablas de consulta o lectura implica un mayor trabajo de desarrollo y debe adoptarse coherencia final. Sin embargo, los requisitos de rendimiento y alta escalabilidad en escenarios de colaboración (o escenarios competitivos , según el punto de vista) son donde debe aplicar CQRS con varias bases de datos.

"Datos en frío" en bases de datos centrales. En el caso de informes complejos y consultas que podrían no requerir datos en tiempo real, un enfoque común consiste en exportar los "datos activos" (datos transaccionales de los microservicios) como "datos inactivos" en bases de datos grandes que solo se usan para los informes. Ese sistema central de bases de datos puede ser un sistema basado en macrodatos, como Hadoop; un almacenamiento de datos como uno basado en Azure SQL Data Warehouse; o incluso una base de datos SQL única que se usa solo para los informes (si el tamaño no es un problema).

Tenga en cuenta que esta base de datos centralizada solo se usaría para consultas e informes que no necesitan datos en tiempo real. Las actualizaciones y las transacciones originales, como origen confiable, deben estar en los datos de microservicios. La forma en que sincronizaría los datos sería mediante la comunicación controlada por eventos (que se trata en las secciones siguientes) o mediante otras herramientas de importación y exportación de la infraestructura de base de datos. Si usa la comunicación controlada por eventos, ese proceso de integración sería similar a la forma en que propaga los datos tal como se describió anteriormente para las tablas de consulta CQRS.

Sin embargo, si el diseño de la aplicación implica agregar constantemente información de varios microservicios para consultas complejas, podría ser un síntoma de un diseño incorrecto -a microservicio debe estar lo más aislado posible de otros microservicios. (Esto excluye informes o análisis que siempre deben usar bases de datos centrales de datos en frío). Tener este problema a menudo podría ser una razón para combinar microservicios. Debe equilibrar la autonomía de la evolución y la implementación de cada microservicio con dependencias fuertes, cohesión y agregación de datos.

Desafío 3: Cómo lograr la coherencia en varios microservicios

Como se indicó anteriormente, los datos propiedad de cada microservicio son privados para ese microservicio y solo se puede acceder a ellos mediante su API de microservicios. Por lo tanto, un desafío presentado es cómo implementar procesos empresariales de un extremo a otro, a la vez que se mantiene la coherencia entre varios microservicios.

Para analizar este problema, echemos un vistazo a un ejemplo de la aplicación de referencia eShopOnContainers. El microservicio Catalog mantiene información sobre todos los productos, incluido el precio del producto. El microservicio Basket administra datos temporales sobre los productos que los usuarios agregan a sus cestas de compras, lo que incluye el precio de los artículos en el momento en que se agregaron a la cesta. Cuando el precio de un producto se actualiza en el catálogo, ese precio también debe actualizarse en las cestas activas que contienen ese mismo producto, además del sistema probablemente debería advertir al usuario que dice que el precio de un artículo determinado ha cambiado desde que lo agregó a su cesta.

En una versión hipotética monolítica de esta aplicación, cuando el precio cambia en la tabla de productos, el subsistema de catálogo podría simplemente usar una transacción ACID para actualizar el precio actual en la tabla Basket.

Sin embargo, en una aplicación basada en microservicios, las tablas Product y Basket son propiedad de sus respectivos microservicios. Ningún microservicio nunca debe incluir tablas o almacenamiento propiedad de otro microservicio en sus propias transacciones, ni siquiera en consultas directas, como se muestra en la figura 4-9.

Diagrama que muestra que los datos de la base de datos de microservicios no se pueden compartir.

Figura 4-9. Un microservicio no puede acceder directamente a una tabla en otro microservicio

El microservicio Catalog no debe actualizar directamente la tabla Basket, ya que la tabla Basket es propiedad del microservicio Basket. Para realizar una actualización del microservicio Basket, el microservicio Catálogo debe usar la coherencia eventual, probablemente basada en la comunicación asincrónica, como mediante eventos de integración (comunicación basada en mensajes y eventos). Así es como la aplicación de referencia eShopOnContainers realiza este tipo de coherencia entre microservicios.

Como indica el teorema CAP, debe elegir entre disponibilidad y coherencia ACID. La mayoría de los escenarios basados en microservicios exigen disponibilidad y alta escalabilidad, en lugar de una coherencia fuerte. Las aplicaciones de misión crítica deben permanecer en funcionamiento, y los desarrolladores pueden sortear la consistencia fuerte utilizando técnicas para trabajar con consistencia débil o eventual. Este es el enfoque adoptado por la mayoría de las arquitecturas basadas en microservicios.

Además, las transacciones de confirmación de dos fases o de estilo ACID no solo se aplican a los principios de microservicios; La mayoría de las bases de datos NoSQL (como Azure Cosmos DB, MongoDB, etc.) no admiten transacciones de confirmación en dos fases, típicas en escenarios de bases de datos distribuidas. Sin embargo, mantener la coherencia de los datos entre servicios y bases de datos es esencial. Este desafío también está relacionado con la cuestión de cómo propagar los cambios en varios microservicios cuando determinados datos deben ser redundantes, por ejemplo, cuando necesita tener el nombre o la descripción del producto en el microservicio catalog y el microservicio Basket.

Una buena solución para este problema es usar la coherencia final entre los microservicios articulados a través de la comunicación controlada por eventos y un sistema de publicación y suscripción. Estos temas se tratan en la sección Comunicación asincrónica controlada por eventos más adelante en esta guía.

Desafío n.º 4: Cómo diseñar la comunicación entre límites de microservicios

La comunicación entre límites de microservicios es un desafío real. En este contexto, la comunicación no hace referencia al protocolo que debe usar (HTTP y REST, AMQP, mensajería, etc.). En su lugar, aborda qué estilo de comunicación debe usar y, especialmente, cómo de acoplados deben estar los microservicios. Según el nivel de acoplamiento, cuando se produzca un error, el impacto de ese error en el sistema variará significativamente.

En un sistema distribuido como una aplicación basada en microservicios, con tantos artefactos que se mueven y con servicios distribuidos entre muchos servidores o hosts, los componentes finalmente producirán un error. Se producirán errores parciales e incluso interrupciones mayores, por lo que debe diseñar los microservicios y la comunicación entre ellos teniendo en cuenta los riesgos comunes en este tipo de sistema distribuido.

Un enfoque popular es implementar microservicios basados en HTTP (REST), debido a su simplicidad. Un enfoque basado en HTTP es perfectamente aceptable; el problema aquí está relacionado con cómo se usa. Si usa solicitudes y respuestas HTTP solo para interactuar con los microservicios desde aplicaciones cliente o desde puertas de enlace de API, es correcto. Pero si crea cadenas largas de llamadas HTTP sincrónicas a través de microservicios, comunicándose a través de sus límites como si los microservicios fueran objetos en una aplicación monolítica, la aplicación finalmente se encontrará con problemas.

Por ejemplo, imagine que la aplicación cliente realiza una llamada API HTTP a un microservicio individual, como el microservicio Ordering. Si el microservicio Ordering a su vez llama a microservicios adicionales mediante HTTP dentro del mismo ciclo de solicitud/respuesta, va a crear una cadena de llamadas HTTP. Podría sonar razonable inicialmente. Sin embargo, hay puntos importantes que se deben tener en cuenta al seguir este camino.

  • Bloqueo y bajo rendimiento. Debido a la naturaleza sincrónica de HTTP, la solicitud original no obtiene una respuesta hasta que finalizan todas las llamadas HTTP internas. Imagine si el número de estas llamadas aumenta significativamente y al mismo tiempo se bloquea una de las llamadas HTTP intermedias a un microservicio. El resultado es que el rendimiento se ve afectado y la escalabilidad general se verá afectada exponencialmente a medida que aumentan las solicitudes HTTP adicionales.

  • Acoplamiento de microservicios con HTTP. Los microservicios empresariales no deben acoplarse con otros microservicios empresariales. Idealmente, no deben "saber" la existencia de otros microservicios. Si la aplicación se basa en microservicios de acoplamiento como en el ejemplo, lograr la autonomía por microservicio será casi imposible.

  • Error en un microservicio. Si implementó una cadena de microservicios vinculados por llamadas HTTP, cuando se produce un error en cualquiera de los microservicios (y finalmente se producirá un error) se producirá un error en toda la cadena de microservicios. Un sistema basado en microservicios debe diseñarse para seguir funcionando así como sea posible durante los errores parciales. Incluso si implementa lógica de cliente que usa reintentos con mecanismos de retroceso exponencial o cortacircuitos, cuanto más complejas sean las cadenas de llamadas HTTP, más compleja es implementar una estrategia de gestión de errores basada en HTTP.

De hecho, si los microservicios internos se comunican mediante la creación de cadenas de solicitudes HTTP como se describe, se podría afirmar que tiene una aplicación monolítica, pero una basada en HTTP entre procesos en lugar de mecanismos de comunicación intraproceso.

Por lo tanto, para aplicar la autonomía de los microservicios y tener una mejor resistencia, debe minimizar el uso de cadenas de comunicación de solicitud/respuesta entre microservicios. Se recomienda usar solo la interacción asincrónica para la comunicación entre microservicios, ya sea mediante la comunicación asincrónica basada en mensajes y eventos, o mediante sondeo HTTP (asincrónico) independientemente del ciclo de solicitud/respuesta HTTP original.

El uso de la comunicación asincrónica se explica con detalles adicionales más adelante en esta guía en las secciones Integración asincrónica de microservicios exige la autonomía del microservicio y la comunicación asincrónica basada en mensajes.

Recursos adicionales