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:
- Un servicio que realmente actualiza la contraseña del usuario
- Un servicio que actualiza el historial de contraseñas del usuario.
- 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
Respuestas:
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
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é?
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.
fuente
En su caso, no puede procesar las tres cosas a la vez. Lo que necesitas es un proceso. Aquí hay un ejemplo extremadamente simplificado:
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:
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
New
estado y procesaUserPasswordHasBeenUpdated
, cambia su estado aUserPasswordHasBeenUpdated
(o cualquier nombre de estado que funcione para usted). En caso de que la orden de compra todavía esté en unaUserPasswordHasBeenUpdated
yUserPasswordHasBeenUpdated
llegue 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:
Envíalo como máximo una vez
Con esta opción, cuando el PO alcanza el
UserPasswordHistoryHasBeenSaved
estado, 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 elUserPasswordHistoryHasBeenSaved
estado 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
UserPasswordHistoryHasBeenSaved
y 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 cambiaUserPasswordHistoryHasBeenSaved
y 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 nuevoUserPasswordHistoryHasBeenSaved
estado, otro mensaje delUserPasswordHistoryHasBeenSaved
mismo 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.
fuente
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.
fuente
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?
fuente
No estoy de acuerdo con muchas de las respuestas.
Hay otras promesas de coherencia que puede agregar.
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 )
fuente
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.