¿Patrones para mantener la coherencia en un sistema distribuido de origen de eventos?

12

He estado leyendo sobre el abastecimiento de eventos últimamente y realmente me gustan las ideas detrás de él, pero estoy atrapado con el siguiente problema.

Digamos que tiene N procesos concurrentes que reciben comandos (por ejemplo, servidores web), generan eventos como resultado y los almacenan en una tienda centralizada. Supongamos también que todo el estado de aplicación transitoria se mantiene en la memoria de los procesos individuales mediante la aplicación secuencial de eventos desde la tienda.

Ahora, supongamos que tenemos la siguiente regla comercial: cada usuario distinto debe tener un nombre de usuario único.

Si dos procesos reciben un comando de registro de usuario para el mismo nombre de usuario X, ambos verifican que X no está en su lista de nombres de usuario, la regla valida para ambos procesos y ambos almacenan un evento "nuevo usuario con nombre de usuario X" en la tienda .

Ahora hemos entrado en un estado global inconsistente porque se viola la regla de negocios (hay dos usuarios distintos con el mismo nombre de usuario).

En un sistema de estilo tradicional de servidor N <-> 1 RDBMS, la base de datos se usa como un punto central de sincronización que ayuda a prevenir tales inconsistencias.

Mi pregunta es: ¿cómo los sistemas de origen de eventos suelen abordar este problema? ¿Simplemente procesan cada comando secuencialmente (por ejemplo, limitan la cantidad de proceso que puede escribir en la tienda a 1)?

Olivier Lalonde
fuente
1
¿Esta restricción está controlada por código o es una restricción db? N eventos pueden o no ser despachados-procesados ​​en secuencia ... N eventos pueden pasar por validaciones al mismo tiempo sin desviarse entre sí. Si el orden es importante, deberá sincronizar la validación. O para usar la cola para poner en cola los eventos
envíalos
@Laiv a la derecha. Para simplificar, supuse que no había una base de datos, todo el estado se guardaba en la memoria. Procesar tipos específicos de comandos secuencialmente a través de una cola sería una opción, pero parece que podría ser complejo decidir qué comandos pueden afectar causalmente a otros y probablemente terminaría colocando todos los comandos en la misma cola, lo que equivale a tener un solo proceso procesando comandos : / Por ejemplo, si tengo un usuario que agrega comentarios en una publicación de blog, "eliminar usuario", "suspender usuario", "eliminar publicación de blog", "deshabilitar comentarios de publicaciones de blog", etc., deberían ir todos en la misma cola.
Olivier Lalonde
1
Estoy de acuerdo con usted, trabajar con colas o semáforos no es simple. Tampoco trabajar con concurrencia o patrones de origen de eventos. Pero básicamente todas las soluciones terminan con un sistema que organiza el tráfico del evento. Sin embargo, es un paradigma interesante. También hay cachés externos orientados a tuplas como Redis que podrían ayudar a administrar este tráfico entre nodos, como almacenar en caché el último estado de una entidad o si dicha entidad se está procesando en este momento. Los cachés compartidos son bastante comunes en este tipo de desarrollos. Puede parecer complejo, pero no te rindas ;-) es bastante interesante
Laiv

Respuestas:

6

En un sistema de estilo tradicional de servidor N <-> 1 RDBMS, la base de datos se usa como un punto central de sincronización que ayuda a prevenir tales inconsistencias.

En los sistemas de origen de eventos, la "tienda de eventos" cumple la misma función. Para un objeto de origen de evento, su escritura es un anexo de sus nuevos eventos a una versión particular de la secuencia de eventos. Entonces, al igual que con la programación concurrente, puede adquirir un bloqueo en ese historial al procesar el comando. Es más común que los sistemas de origen de eventos adopten un enfoque más optimista: cargue el historial anterior, calcule el nuevo historial y luego compare e intercambie. Si algún otro comando también ha escrito en esa secuencia, entonces su comparación e intercambio falla. A partir de ahí, puede volver a ejecutar su comando, o abandonar su comando, o tal vez incluso fusionar sus resultados en el historial.

La contención se convierte en un problema importante si todos los N servidores con sus comandos M intentan escribir en una sola secuencia. La respuesta habitual aquí es asignar un historial a cada entidad de origen de eventos en su modelo. Por lo tanto, el usuario (Bob) tendría un historial distinto del usuario (Alice), y las escrituras a una no bloquearán las escrituras a la otra.

Mi pregunta es: ¿cómo los sistemas de origen de eventos suelen abordar este problema? ¿Simplemente procesan cada comando secuencialmente?

Greg Young en Validación de set

¿Existe una manera elegante de verificar restricciones únicas en los atributos de los objetos de dominio sin mover la lógica empresarial a la capa de servicio?

La respuesta breve, en muchos casos, investigando ese requisito revela más profundamente que (a) es un proxy mal entendido para algún otro requisito, o (b) que las violaciones de la "regla" son aceptables si pueden detectarse (informe de excepción) , mitigados dentro de una ventana de tiempo, o son de baja frecuencia (por ejemplo: los clientes pueden verificar si hay un nombre disponible antes de enviar un comando para usarlo).

En algunos casos, cuando su tienda de eventos es buena para establecer la validación (es decir, una base de datos relacional), entonces implementa el requisito escribiendo en una tabla de "nombres únicos" en la misma transacción que persiste los eventos.

En algunos casos, solo puede hacer cumplir el requisito haciendo que todos los nombres de usuario se publiquen en la misma secuencia (que le permite evaluar el conjunto de nombres en la memoria, como parte de su modelo de dominio). - En este caso, dos procesos actualizarán el intento de actualizar "el" historial de flujo, pero una de las operaciones de comparar e intercambiar fallará, y el reintento de ese comando podrá detectar el conflicto.

VoiceOfUnreason
fuente
1) Gracias por las sugerencias y referencias. Cuando dice "comparar y cambiar", ¿quiere decir que el proceso, al momento de almacenar un evento, detectaría nuevos eventos desde que comenzó a procesar el comando? Supongo que esto requeriría una tienda de eventos que admita semántica de "comparar e intercambiar", ¿correcto? (por ejemplo, "escriba este evento solo y solo si el último evento tiene ID X")?
Olivier Lalonde
2) También me gusta la idea de aceptar inconsistencias temporales y repararlas eventualmente, pero no estoy seguro de cómo codificaría eso de manera confiable ... tal vez tenga un proceso dedicado que valide eventos secuencialmente y cree eventos de reversión cuando detecte ¿algo salió mal? ¡Gracias!
Olivier Lalonde
(1) Diría "nueva versión de la historia" en lugar de "nuevos eventos", pero usted tiene la idea; solo reemplace la historia si es la que estamos esperando.
VoiceOfUnreason
(2) Sí. Es un poco de lógica que lee los eventos de la tienda en lotes, y al final del lote emite un informe de excepción ("tenemos demasiados usuarios llamados Bob"), o envía comandos para compensar el problema (suponiendo que la respuesta correcta sea computable sin intervención humana).
VoiceOfUnreason
2

Parece que podría implementar un proceso de negocio ( sagaen contexto de Domain Driven Design) para el registro del usuario donde el usuario es tratado como un CRDT.

Recursos

  1. https://doc.akka.io/docs/akka/current/distributed-data.html http://archive.is/t0QIx

  2. "CRDT con datos distribuidos de Akka" https://www.slideshare.net/markusjura/crdts-with-akka-distributed-data para obtener más información

    • CmRDTs - CRDT basados ​​en operaciones
    • CvRDTs - CRTD basados ​​en estado
  3. Ejemplos de código en Scala https://github.com/akka/akka-samples/tree/master/akka-sample-distributed-data-scala . Quizás el "carrito de compras" sea el más adecuado.

  4. Tour of Akka Cluster - Akka Distributed Data https://manuel.bernhardt.io/2018/01/03/tour-akka-cluster-akka-distributed-data/
Semántica
fuente