Microservicios: manejo de la consistencia eventual

22

Supongamos que tenemos una función que actualiza la contraseña de un usuario.

Una vez que se hace clic en el botón 'Actualizar contraseña', se envía un evento UpdatePasswordEvent a un tema donde se suscriben otros 3 servicios:

  1. Un servicio que realmente actualiza la contraseña del usuario
  2. Un servicio que actualiza el historial de contraseñas del usuario.
  3. Un servicio que envía un correo electrónico informando al usuario que su contraseña ha sido modificada.

Según lo que he entendido acerca de la coherencia eventual, todos estos servicios (consumidores) recibirán el evento al mismo tiempo y los procesarán por separado, lo que, en un buen escenario, hará que los datos sean consistentes.

Sin embargo, ¿qué sucede si un servicio no puede procesar el evento? por ejemplo, desconexión repentina, error de la base de datos, etc. ¿Cuál es un buen patrón / práctica para manejar estas fallas de transacción?

Estaba pensando en crear un RollbackTopic donde, si algún evento no se puede procesar, se creará un RollbackEvent en un tema donde los "servicios de reversión" harán su trabajo y revertirán los datos

mpmp
fuente
11
No puede deshacer un correo electrónico enviado :-)
Laiv
2
Porque todos deberían ser parte del mismo servicio. El micro servicio se opone a los monolitos, no significa que tenga que diseñarlos lo menos "físicamente" posible. Aunque esto no está directamente relacionado, debería leer esta pregunta y las dos respuestas principales: softwareengineering.stackexchange.com/questions/339230/…
Walfrat
1
Es posible que desee considerar la actualización de la contraseña del usuario en la base de datos de forma síncrona, de modo que proporcione comentarios inmediatos al usuario y active otros servicios de forma asincrónica emitiendo un mensaje de que la contraseña cambió en un tema, para que su mensaje no tenga que contener la contraseña
cr3
¿Es el correo electrónico para decirle al usuario que la transacción se ha completado, o está ahí para decirle al usuario que alguien (con suerte) ha cambiado la contraseña? “Si no fuiste tú, entonces debes actuar”. Si es el segundo, simplemente envíe un correo electrónico ahora, lo mejor que pueda.
ctrl-alt-delor

Respuestas:

29

Según lo que he entendido sobre la coherencia eventual, todos estos servicios (consumidores) recibirán el evento al mismo tiempo y los procesarán por separado , lo que, en un buen escenario, hará que los datos sean consistentes.

No, no necesariamente Como comenté, no podemos deshacer un correo electrónico enviado, por lo que aún necesitamos una especie de "secuencia". IPC sobre la gestión de datos basada en eventos no está exento de la orquestación 1 .

Por ejemplo, el correo electrónico no debe enviarse a menos que las transacciones anteriores finalicen con éxito y el servicio de correo electrónico obtenga una prueba de ello. 3

Sin embargo, ¿qué sucede si un servicio no puede procesar el evento? por ejemplo, desconexión repentina, error de la base de datos, etc. ¿Cuál es un buen patrón / práctica para manejar estas fallas de transacción?

Saluda a las falacias de la informática distribuida . Son lo que complica las cosas y, como de costumbre, no hay balas de plata para tratar con ellos.

Antes de comenzar nuestro viaje en busca del Arca Perdida, primero debemos considerar preguntarle a la organización. A menudo, la solución está en cómo la organización enfrenta estos problemas en el mundo real .

¿Qué hacen todos (departamentos) cuando faltan ciertos datos o están incompletos?

Nos daremos cuenta de que los diferentes departamentos tienen diferentes soluciones que, en conjunto, comprenden la solución a implementar.

De todos modos, aquí hay algunas prácticas que podrían ayudarnos con la estrategia a seguir.

Consistencia eventual

En lugar de garantizar que el sistema esté en un estado constante todo el tiempo, podemos aceptar que el sistema lo obtendrá en algún momento en el futuro. Este enfoque es especialmente útil para operaciones comerciales de larga duración.

La forma en que el sistema alcanza la consistencia varía de un sistema a otro. Puede implicar desde procesos automatizados hasta algún tipo de intervención humana. Por ejemplo, el típico intento de nuevo más tarde o el contacto con el Servicio al Cliente .

Abortar todas las operaciones

Vuelva a colocar el sistema en un estado coherente mediante transacciones de compensación . Sin embargo, tenemos que tener en cuenta que estas transacciones también pueden fallar, lo que podría llevarnos a un punto donde la inconsistencia es aún más difícil de resolver. Y, nuevamente, no podemos deshacer un correo electrónico enviado.

Para un número bajo de transacciones, este enfoque es factible, porque el número de transacciones compensatorias también es bajo. Si hubiera varias transacciones comerciales involucradas en el IPC, manejar una transacción compensatoria para cada una de ellas sería un desafío.

Si buscamos transacciones de compensación , encontraremos que el patrón de diseño del interruptor automático es muy útil, y obligatorio, me atrevería a decir :

Transacciones distribuidas

La idea es abarcar múltiples transacciones dentro de una sola transacción, a través de un proceso general de gobierno conocido como Transaction Manager . Un algoritmo común para manejar transacciones distribuidas es la confirmación en dos fases .

La principal preocupación de las transacciones distribuidas es que confían en bloquear los recursos durante su vida útil y, como sabemos, las cosas también pueden salir mal para el Administrador de transacciones .

Si los administradores de transacciones se ven comprometidos, podemos terminar con varios bloqueos en los diferentes contextos delimitados, lo que resulta en comportamientos inesperados debido a la puesta en cola de los mensajes. 2

Operaciones de descomposición. ¿Por qué?

Si está descomponiendo un sistema existente y encuentra una colección de conceptos que realmente quieren estar dentro de un límite de transacción única, quizás los deje para el final.

Sam Newman

En la línea con los argumentos anteriores, Sam -en su libro Building Microservices- declara que, si realmente no podemos permitirnos la coherencia final, deberíamos evitar dividir la operación ahora.

Si no podemos permitirnos dividir ciertas operaciones en dos o más transacciones, podría decirse que, probablemente, estas transacciones pertenecen al mismo contexto acotado, o, al menos, a un contexto transversal que aún no se ha modelado.

Por ejemplo, en nuestro caso, nos damos cuenta de que las transacciones n. ° 1 y n. ° 2 están estrechamente relacionadas entre sí y probablemente ambas podrían pertenecer al mismo contexto acotado Cuentas , usuarios , registro , lo que sea ...

Considere colocar ambas operaciones dentro de los límites de la misma transacción. Haría que toda la operación sea más fácil de manejar. También pesa el nivel de criticidad de cada transacción. Probablemente, si la transacción # 2 falla, no debe comprometer toda la operación. En caso de dudas consultar con la organización .


1: No es el tipo de orquestación que piensas. No estoy hablando de la orquestación de ESB. Estoy hablando de hacer que los servicios reaccionen al evento apropiado.

2: Puede encontrar interesantes opiniones de Sam Newman con respecto a las transacciones distribuidas.

3: Verifique la respuesta de David Parker con respecto a este tema.

Laiv
fuente
3
Muy buena respuesta. Solo enfatizaría la importancia de tener en cuenta los riesgos que surgen cuando se usan transacciones distribuidas, principalmente el bloqueo de recursos que produce puntos muertos y paradas de sistemas. En un producto de comercio electrónico en el que trabajé hace aproximadamente 3 años, tuvimos que reemplazar los DT con un sistema de mensajería, porque con la cantidad de usuarios disponibles en los sistemas, el sistema era muy propenso a errores. Los problemas con los DT se producen principalmente cuando crece una base de usuarios.
Andy
7

En su caso, no puede procesar las tres cosas a la vez. Lo que necesitas es un proceso. Aquí hay un ejemplo extremadamente simplificado:

Comando y orquestación de eventos

Es importante saber que las operaciones de alteración de estado DEBEN realizarse siempre en una entidad coherente. A menos que pueda garantizar una fuerte consistencia , debe hacerse en un registro maestro.

Su sistema debe garantizar que antes de que se produzca cualquier evento en su sistema, los cambios DEBEN persistir primero con la seguridad transaccional. Esto es para asegurar que un evento generado realmente sea una confirmación de lo que realmente sucedió.

Hay varias partes complicadas del proceso tal como están y voy a ignorar las obvias, como: ¿Qué sucede si su servidor de base de datos muere cuando persiste a un usuario con una contraseña cambiada? Simplemente emite UpdatePassword nuevamente. Sin embargo, algunas partes deben ser atendidas por usted, y estas son:

  • manejo de duplicación de mensajes,
  • manejo de envío de correo electrónico.

En un sistema, el orquestador de procesos (PO) no es otra cosa que otra entidad, que contiene un estado interno, también en el término literal, y permite transiciones entre los estados, actuando efectivamente como algún tipo de máquina de estados. Gracias al estado interno, puede eliminar el procesamiento de duplicación de mensajes.

Cuando el PO está en un Newestado y procesa UserPasswordHasBeenUpdated, cambia su estado a UserPasswordHasBeenUpdated(o cualquier nombre de estado que funcione para usted). En caso de que la orden de compra todavía esté en una UserPasswordHasBeenUpdatedy UserPasswordHasBeenUpdatedllegue otra , ignorará por completo el mensaje, sabiendo que es una duplicación. Mecanismo similar se implementaría también para otros estados.

Manejar el envío real del correo electrónico es un poco más complicado. Aquí tienes dos opciones:

  1. envíalo como máximo una vez,
  2. envíalo al menos una vez.

Envíalo como máximo una vez

Con esta opción, cuando el PO alcanza el UserPasswordHistoryHasBeenSavedestado, se envía un comando para enviar un correo electrónico como reacción al cambio de estado. Su sistema se aseguraría de que el UserPasswordHistoryHasBeenSavedestado persistiera antes de enviar el correo electrónico, es decir, un mensaje duplicado no activaría el envío de correo electrónico nuevamente. Con este enfoque, se asegura de que se guarda el estado correcto para la orden de compra, pero no puede garantizar ninguna operación posterior.

Envíalo al menos una vez

Esto es lo que buscaría.

En lugar de guardar UserPasswordHistoryHasBeenSavedy enviar el correo electrónico como reacción, intente enviar el correo electrónico primero. Si la operación de envío falla, el estado del PO nunca cambia UserPasswordHistoryHasBeenSavedy todavía se procesa otro mensaje del mismo tipo. En caso de que el envío del correo electrónico realmente tenga éxito, pero su sistema fallará durante la persistencia de la orden de compra con su nuevo UserPasswordHistoryHasBeenSavedestado, otro mensaje del UserPasswordHistoryHasBeenSavedmismo activará una vez más el comando para enviar el correo electrónico y el usuario lo habrá recibido varias veces .

En su caso, desea asegurarse de que el usuario realmente reciba el correo electrónico. Es por eso que elegiría las segundas opciones sobre las primeras.

Andy
fuente
2

Los sistemas de colas no son tan frágiles como podría pensar.

Si estuviéramos escribiendo los tres procesos en una base de datos relacional, podríamos usar una transacción para manejar una falla de procesos intermedios.

Sin el compromiso final, el trabajo parcial sería descartado.

En un sistema de bases de colas, tendrá opciones similares cuando lea un mensaje de la cola para manejar fallas intermedias del proceso.

Amazon SQS, por ejemplo, simplemente oculta los mensajes que se leen. a menos que se envíe un comando Eliminar final, el mensaje volverá a aparecer o se colocará en una cola de mensajes no entregados.

Puede implementar 'transacciones' similares de varias maneras, esencialmente manteniendo una copia del mensaje hasta que reciba la confirmación del procesamiento exitoso. Si la confirmación no se recibe a tiempo. puede enviar el mensaje nuevamente o guardarlo para atención manual.

Potencialmente, podría crear un 'servicio de reversión' que supervisara estos mensajes con errores, conociera los mensajes relacionados y el estado pasado y realizara una reversión.

¡Sin embargo! Por lo general, es mejor volver a enviar los mensajes con errores. Después de todo, estos tienden a ser casos extremos. O bien un servidor falló catastróficamente o hubo un error en el manejo de un tipo de mensaje en particular.

Una vez alertado del error, el servicio puede repararse y los mensajes procesarse con éxito. Devolviendo el sistema como un todo a un estado consistente.

Ewan
fuente
2

Lo que estás enfrentando aquí es el problema de los dos generales . En esencia: ¿cómo puede estar seguro de que se recibe un mensaje y se produce una respuesta a ese mensaje? En muchos casos, no existe una solución perfecta. De hecho, en un sistema distribuido a menudo es imposible obtener una entrega de mensajes exactamente una vez .

Una primera observación obvia es que el servicio que cambia la contraseña debería enviar el evento de cambio de contraseña. De esta forma, el historial de contraseñas y los servicios de envío de correo solo se activan cuando la contraseña realmente cambia, independientemente de por qué cambió.

Para resolver realmente su problema, no consideraría las transacciones distribuidas, sino que miraría en la dirección de al menos una vez la entrega de mensajes y el procesamiento idempotente.

  • Al menos una vez

    Para asegurarse de que todos los consumidores vean el evento de cambio de contraseña, debe utilizar un canal de comunicación duradero donde los mensajes se puedan consumir en un estilo "al menos una vez". Los consumidores solo reconocen un mensaje como consumido cuando lo han procesado completamente. Si, por ejemplo, el servicio de historial de contraseñas se bloquea al escribir una entrada en el historial, volverá a leer el mismo evento de cambio de contraseña después de reiniciar e intentará nuevamente, reconociendo ese evento como de solo lectura después de que se haya escrito en el historial. Debe elegir una solución de cola de mensajes en función de su capacidad para reenviar mensajes hasta que sean reconocidos.

  • Idempotencia

    Después de lograr al menos una entrega, existe el problema de acciones duplicadas que ocurren cuando un mensaje se procesó parcialmente antes de que el consumidor sea interrumpido y luego reprocesado más tarde. Eso debería resolverse diseñando cada servicio para que sea idempotente. O las escrituras que realiza pueden ocurrir varias veces sin efectos adversos, o guarda su propio almacén de las acciones que tomó y evita realizar una acción más de una vez. En el caso del envío de correo, descubrirá que probablemente no valga la pena intentar que se comporte de manera despectiva y que simplemente esté bien con que ocasionalmente se envíe un correo dos veces.

En cualquier caso, ten cuidado con lo micro que haces tus servicios ¿Su servicio de historial de contraseñas realmente necesita ser independiente del servicio de cambio de contraseñas?

Joeri Sebrechts
fuente
1

No estoy de acuerdo con muchas de las respuestas.

  1. Envíe el correo electrónico ahora “Alguien ha cambiado su contraseña. Si eras tú, entonces no necesitas hacer nada. Si no entra en pánico ”. Esto llegará cuando llegue.
  2. Cambia la contraseña. Aunque tienes eventual consistencia. Desea asegurarse de que esta sesión vea los cambios realizados por el usuario.

Hay otras promesas de coherencia que puede agregar.

  • Asegúrese de que los cambios sucedan en orden de tiempo.
  • Asegúrese de que un usuario nunca vea una reversión, pero que otros usuarios aún no vean el cambio.
  • Hay otros

Estas consistencias adicionales deberán implementarse dependiendo de los hechos de la aplicación.


No tengo idea de lo que quieres decir con "actualiza el historial", pero nunca cambies el historial. Si solo está extendiendo el DAG, esto debería causar el cambio en el estado actual. No son independientes. Si lo son, entonces no puede confiar en la historia que refleja lo que sucedió. (y por último pero no menos importante, no almacene contraseñas, vea cómo no almacenar contraseñas )

ctrl-alt-delor
fuente
Si puede enviar el correo electrónico al principio, entonces su enfoque está bien. Si tiene que enviar algo junto con el correo electrónico. Tal vez una especie de enlace / datos que solo se puedan obtener después de lograr la consistencia, entonces no puede enviar el correo electrónico primero. Eso es lo que comenté consider asking the organization first.. Probablemente tengas razón. Sin embargo, he encontrado que es importante condicionar esos eventos que no podemos deshacer. Por ejemplo, notificaciones al usuario final. La notificación sobre el estado real de los datos del usuario podría generar una mala impresión.
Laiv
Dicho esto, para este escenario específico (notificación de cambio de contraseña), estuve de acuerdo con este enfoque. Tan pronto como se ajuste a los requisitos.
Laiv