¿Cuál es la forma correcta de sincronizar datos a través de microservicios?

19

Soy relativamente nuevo en la arquitectura de microservicios. Tenemos una aplicación web de tamaño moderado y estoy sopesando los pros y los contras de dividirla en microservicios en lugar de un sistema monolítico que ahora estamos avanzando.

Por lo que yo entiendo, considere los microservicios Ay Bcada uno de los cuales depende de un subconjunto de datos que tiene el otro. Si se publica un mensaje Adiciendo que algo ha cambiado, Bpuede consumir ese mensaje y replicar una copia local de Ala información y usarla para hacer lo que sea Bnecesario.

Sin embargo, ¿qué Bpasa si falla / falla y, después de un tiempo, vuelve a subir? Durante ese tiempo de inactividad, Aha publicado dos mensajes más. ¿Cómo Bsabe cómo actualizar su copia local de Ala información de?

De acuerdo, si Bes el único consumidor de Ala cola, puede comenzar a leerla una vez que vuelva a estar en línea, pero ¿qué pasa si hay otros consumidores de esa cola y se consumen esos mensajes?

Como ejemplo más concreto, si un Usersservicio tiene su dirección de correo electrónico actualizada mientras un Billingmicroservicio está inactivo, si el Billingmicroservicio vuelve a funcionar, ¿cómo sabe que el correo electrónico se ha actualizado?

Cuando los microservicios vuelven a funcionar, ¿se transmite diciendo "Hola, estoy de vuelta, dame toda tu información actual?"

En general, ¿cuáles serían las mejores prácticas de la industria para la sincronización de datos?

noblerare
fuente
1
Para evitarlo siempre que sea posible.
Telastyn
1
¿Por qué Ordersnecesita saber algo al respecto Users?
kdgregory
Es solo un ejemplo. Reemplace los dos con lo que quiera que tenga sentido.
noblerare
un enrutamiento en abanico resolverá su problema de 'mensaje es consumido por otra persona'. pero realmente no está claro lo que está tratando de lograr.
Ewan
@Ewan He actualizado mi publicación original para explicar mejor lo que estoy tratando de preguntar.
noblerare

Respuestas:

5

Desafiaría toda su idea de "enviar los datos a todos los demás microservicios".

Por lo general, si un servicio de facturación necesita una dirección de correo electrónico, solo le pide al servicio de dirección la dirección de correo electrónico del cliente específico. No necesita mantener una copia de todos los datos de la dirección ni se le informará si algo cambia. Simplemente pregunta y obtiene la respuesta de los datos más recientes.

J. Fabian Meier
fuente
Creo que esta respuesta es exactamente correcta. Elimina muchos problemas relacionados con la sincronización. De hecho, estoy mirando el código en este momento que tiene tales problemas porque diferentes servicios guardan copias de la información y tienen tales problemas de sincronización.
DaveG
2
Gracias por tu respuesta. Entonces, ¿por qué hay una necesidad de un modelo de pub / sub y colas de mensajes? Si estamos tratando de "extraer" en lugar de "empujar" datos, estamos preocupados por la latencia del servicio.
noblerare
AFAIK, su servicio no necesita reaccionar de inmediato si algo cambia (como en un pub / sub), pero ocasionalmente necesita datos. Entonces simplemente lo tiraría. Si le preocupa la latencia, puede almacenar en caché los datos, pero esto nuevamente tiene el costo de no saber si los datos están actualizados. Si sus archivos son grandes, también puede preguntar si algo cambia antes de volver a extraer algo.
J. Fabian Meier
Tenga en cuenta que esta solución tiene el costo de acoplar estrechamente el servicio dependiente, lo que significa que la dirección de correo electrónico no estará disponible cuando el servicio del usuario no esté disponible. Para empezar, una de las ideas iniciales de dividir los servicios para que sean desplegables, escalables, etc. de forma independiente. Si todos los servicios se comunican directamente entre sí sin un caché o una garantía de alta disponibilidad, cuando un sistema está inactivo, todos bajar.
dukethrash
@dukethrash Entonces hazlos altamente disponibles
J. Fabian Meier
5

Después de investigar un poco más, me topé con este artículo del que saqué algunas citas que creo que son útiles para lo que quiero lograr (y para cualquier lector futuro). Esto ofrece una manera de adoptar un modelo de programación reactiva sobre un modelo de programación imperativo.

Abastecimiento de eventos

La idea aquí es representar la transición de estado de cada aplicación en forma de un evento inmutable. Los eventos se almacenan en forma de registro o diario a medida que ocurren (también conocido como 'almacén de eventos'). También pueden consultarse y almacenarse indefinidamente, con el objetivo de representar cómo el estado de la aplicación, en su conjunto, evolucionó con el tiempo.

Lo que esto ayuda a lograr es que si un microservicio deja de funcionar y se publican otros eventos pertinentes y , por ejemplo, otros eventos de ese microservicio los consumen, cuando ese microservicio vuelve a funcionar, puede referirse a esto event storepara recuperar todos los eventos que se perdió durante el período en que cayó.

Apache Kafka como corredor de eventos

Considere el uso de Apache Kafka, que puede almacenar y enviar miles de eventos por segundo y tiene mecanismos incorporados de replicación y tolerancia a fallas. Tiene un almacén persistente de eventos que pueden almacenarse en el disco de forma indefinida y consumirse en cualquier momento (pero no eliminarse) del Tema (cola de fantasía de Kafka) en el que se entregaron.

A los eventos se les asignan compensaciones que los identifican de manera unívoca dentro del Tema: Kafka puede administrar las compensaciones en sí, proporcionando fácilmente semántica de entrega "como máximo una vez" o "al menos una vez", pero también se pueden negociar cuando un consumidor de eventos se une a un Tema , lo que permite que los microservicios comiencen a consumir eventos desde cualquier lugar arbitrario en el tiempo, generalmente desde donde el consumidor lo dejó. Si el último desplazamiento de evento consumido persiste transaccionalmente en el almacenamiento local de los servicios cuando los casos de uso se "completan con éxito", ese desplazamiento se puede usar fácilmente para lograr una semántica de entrega de evento "exactamente una vez".

De hecho, cuando los consumidores se identifican con Kafka, Kafka registrará qué mensajes fueron entregados a qué consumidor para que no se vuelva a enviar.

Sagas

Para casos de uso más complejos donde la comunicación entre los diferentes servicios es realmente necesaria, la responsabilidad de terminar el caso de uso debe estar bien reconocida: el caso de uso está descentralizado y solo finaliza cuando todos los servicios involucrados reconocen que su tarea se completó con éxito, de lo contrario, todo el caso de uso debe fallar y se deben activar medidas correctivas para deshacer cualquier estado local no válido.

Esto es cuando entra en juego la saga. Una saga es una secuencia de transacciones locales. Cada transacción local actualiza la base de datos y publica un mensaje o evento para activar la próxima transacción local en la saga. Si una transacción local falla porque viola una regla comercial, la saga ejecuta una serie de transacciones compensatorias que deshacen los cambios realizados por las transacciones locales anteriores. Lea esto para más información.

noblerare
fuente
Todavía no entiendo por qué quieres construir una estructura tan complicada. Por lo general, es mucho más fácil si cada servicio solo tiene sus propios datos y se los da a otros servicios a pedido.
J. Fabian Meier
^ Pero reducirá la disponibilidad del sistema. La estructura complicada podría estar justificada si se requiere una alta resistencia.
avmohan
1

Incluso si llego tarde, quisiera poner mis dos centavos en el argumento porque creo que es un punto importante cuando desea evaluar e diseñar una arquitectura de microservicios basada en eventos. Cada microservicio sabe exactamente cuáles son los eventos que impactan en su estado y puede esperarlos. Cuando el microservicio no está disponible, debe haber un componente que guarde los mensajes que se necesitan del microservicio fallido hasta que no pueda "consumirlos". De hecho, este es un modelo de "productor / consumidor" y no uno de "publicación / suscripción". Los corredores de mensajes (como Kafka, RabbitMQ, ActiveMQ, etc.) suelen ser la mejor manera de lograr este comportamiento (a menos que no esté implementando algo diferente, como el abastecimiento de eventos) proporcionando colas persistentes y un mecanismo de bloqueo / bloqueo.

Ahora el microservicio sabe que un mensaje finalmente se entrega pero no es suficiente: ¿cuál es la forma en que espera la entrega de un solo mensaje? ¿Puede administrar la entrega de múltiples copias de la misma notificación de evento? Esto es cuestión de entrega semántica (al menos una vez, exactamente una vez)

Pensamientos finales):

  1. Cuando agrega un microservicio a su arquitectura que necesita consumir eventos de otros, debe hacer la primera sincronización

  2. Incluso el corredor puede fallar, en este caso los mensajes se pierden

Para ambos escenarios, sería útil tener mecanismos simples para rehidratar su estado de microservicio. Podría ser una API REST o un script que envíe mensajes, pero lo más importante es tener medios para realizar alguna tarea de mantenimiento

Carmine Ingaldi
fuente
0

Puede reemplazar una cola de eventos normal con un modelo de editor / suscriptor, donde el Aservicio publique un nuevo mensaje del tema T y el Btipo de microservicios se suscribirá al mismo tema.

Idealmente B, sería un servicio sin estado y utilizaría un servicio de persistencia separado, de modo que una Binstancia de servicio fallida se reemplazaría generando una o más Binstancias de servicio para continuar su trabajo, leyendo el mismo servicio de persistencia compartido.

A.Rashad
fuente
0

Si A publica un mensaje diciendo que algo ha cambiado, B puede consumir ese mensaje y replicar una copia local de la información de A y usarlo para hacer lo que B necesite hacer.

Si desea que B pueda acceder a los datos internos de A, sería mejor simplemente darle acceso a las bases de datos internas de A.

Sin embargo, no debe hacer eso, el objetivo de una arquitectura orientada al servicio es que el servicio B no puede ver el estado interno del servicio A y está limitado a realizar solicitudes a través de las API REST (y viceversa).

En su caso, podría tener un servicio de datos del usuario, que tiene la responsabilidad de almacenar todos los datos del usuario. Otros servicios que desean usar esos datos solo lo solicitan cuando lo necesitan y no guardan una copia local (lo cual, por cierto, es realmente útil si piensa en el cumplimiento del RGPD). El servicio de datos del usuario puede admitir operaciones CRUD simples como "Crear nuevo usuario" o "Cambiar nombre para user_id 23" o puede tener operaciones más complejas, "Encuentre a todos los usuarios estándar con un cumpleaños en las próximas 2 semanas y entrégueles estado de prueba premium ". Ahora, cuando su servicio de facturación necesite enviar un correo electrónico al usuario 42, le preguntará al servicio de datos del usuario "¿Cuál es la dirección de correo electrónico de user_id 42", usará sus datos internos con toda la información de facturación para elaborar el correo electrónico y luego podrá pasar el correo electrónico? dirección de correo electrónico y cuerpo a un servidor de correo.

Helena
fuente