Des modèles pour maintenir la cohérence dans un système distribué, basé sur des événements?

12

J'ai lu récemment sur la recherche d' événements et j'aime vraiment les idées qui se cachent derrière, mais je suis coincé avec le problème suivant.

Supposons que vous ayez N processus simultanés qui reçoivent des commandes (par exemple des serveurs Web), génèrent des événements en conséquence et les stockent dans un magasin centralisé. Supposons également que tous les états d'application transitoires soient conservés dans la mémoire des processus individuels en appliquant séquentiellement les événements du magasin.

Supposons maintenant que nous avons la règle commerciale suivante: chaque utilisateur distinct doit avoir un nom d'utilisateur unique.

Si deux processus reçoivent une commande d'enregistrement d'utilisateur pour le même nom d'utilisateur X, ils vérifient tous les deux que X ne figure pas dans leur liste de noms d'utilisateurs, la règle valide pour les deux processus et ils stockent tous les deux un événement «nouvel utilisateur avec le nom d'utilisateur X» dans le magasin .

Nous sommes maintenant entrés dans un état global incohérent car la règle métier est violée (il y a deux utilisateurs distincts avec le même nom d'utilisateur).

Dans un système de style RDBMS traditionnel à serveur N <-> 1, la base de données est utilisée comme point central de synchronisation, ce qui permet d'éviter de telles incohérences.

Ma question est la suivante: comment les systèmes basés sur des événements abordent-ils généralement ce problème? Traitent-ils simplement toutes les commandes de manière séquentielle (par exemple, limitent-elles à 1 la quantité de processus pouvant écrire dans le magasin)?

Olivier Lalonde
la source
1
Une telle restriction est-elle contrôlée par le code ou est-ce une contrainte db? N événements peuvent ou non être distribués-traités en séquence ... N événements peuvent passer par des validations en même temps sans se séparer. Si la commande est importante, vous devrez synchroniser la validation. Ou pour utiliser la file d'attente pour mettre les événements en file d'attente, envoyez-les en séquence
Laiv
@Laiv droite. Pour simplifier, j'ai supposé qu'il n'y avait pas de base de données, tous les états étant conservés en mémoire. Le traitement séquentiel de types spécifiques de commandes dans une file d'attente serait une option, mais il semble que cela pourrait être complexe de décider quelles commandes peuvent affecter les autres et je finirais probablement par mettre toutes les commandes dans la même file d'attente, ce qui revient à avoir un seul processus de traitement des commandes : / Par exemple, si un utilisateur ajoute un commentaire à un article de blog, les options "supprimer l'utilisateur", "suspendre l'utilisateur", "supprimer l'article de blog", "désactiver les commentaires de l'article de blog", etc. devraient toutes être placées dans la même file d'attente.
Olivier Lalonde
1
Je suis d'accord avec vous, travailler avec des files d'attente ou des sémaphores n'est pas simple. Ni pour travailler avec des modèles de simultanéité ou de source d'événement. Mais fondamentalement, toutes les solutions aboutissent à un système orchestrant le trafic d'un événement. Cependant, c'est un paradigme intéressant. Il existe également des caches externes orientés vers des tuples comme Redis qui pourraient aider à gérer ce trafic entre les nœuds, comme la mise en cache du dernier état d'une entité ou si une telle entité est en cours de traitement en ce moment. Les caches partagés sont assez courants dans ce type de développements. Cela peut sembler complexe mais n'abandonnez pas ;-) c'est assez intéressant
Laiv

Réponses:

6

Dans un système de style RDBMS traditionnel à serveur N <-> 1, la base de données est utilisée comme point central de synchronisation, ce qui permet d'éviter de telles incohérences.

Dans les systèmes provenant d'événements, le "magasin d'événements" joue le même rôle. Pour un objet provenant d'un événement, votre écriture est une annexe de vos nouveaux événements à une version particulière du flux d'événements. Ainsi, tout comme avec la programmation simultanée, vous pouvez acquérir un verrou sur cet historique lors du traitement de la commande. Il est plus courant que les systèmes basés sur des événements adoptent une approche plus optimiste: chargez l'historique précédent, calculez le nouvel historique, puis comparez et échangez. Si une autre commande a également écrit dans ce flux, la comparaison et l'échange échouent. À partir de là, vous réexécutez votre commande, ou abandonnez votre commande, ou peut-être même fusionnez vos résultats dans l'historique.

La contention devient un problème majeur si tous les N serveurs avec leurs commandes M tentent d'écrire dans un seul flux. La réponse habituelle ici consiste à allouer un historique à chaque entité d'origine d'événement de votre modèle. Ainsi, l'utilisateur (Bob) aurait un historique distinct de l'utilisateur (Alice), et les écritures sur l'un ne bloqueront pas les écritures sur l'autre.

Ma question est la suivante: comment les systèmes basés sur des événements abordent-ils généralement ce problème? Traitent-ils simplement toutes les commandes de manière séquentielle?

Greg Young sur la validation du plateau

Existe-t-il un moyen élégant de vérifier les contraintes uniques sur les attributs d'objet de domaine sans déplacer la logique métier dans la couche de service?

Une réponse courte, dans de nombreux cas, une enquête plus approfondie sur cette exigence révèle que soit (a) il s'agit d'un mandataire mal compris pour une autre exigence, ou (b) que les violations de la "règle" sont acceptables si elles peuvent être détectées (rapport d'exception) , atténué dans une certaine fenêtre de temps, ou sont de faible fréquence (par exemple: les clients peuvent vérifier si un nom est disponible avant d'envoyer une commande pour l'utiliser).

Dans certains cas, lorsque votre magasin d'événements est bon pour la validation des ensembles (c'est-à-dire: une base de données relationnelle), vous implémentez l'exigence en écrivant dans une table "noms uniques" dans la même transaction qui persiste les événements.

Dans certains cas, vous ne pouvez appliquer l'exigence qu'en publiant tous les noms d'utilisateur dans le même flux (ce qui vous permet d'évaluer l'ensemble de noms en mémoire, dans le cadre de votre modèle de domaine). - Dans ce cas, deux processus mettront à jour la tentative de mise à jour de "l'historique" du flux, mais l'une des opérations de comparaison et d'échange échouera et la nouvelle tentative de cette commande pourra détecter le conflit.

VoiceOfUnreason
la source
1) Merci pour les suggestions et références. Lorsque vous dites «comparer et échanger», voulez-vous dire que le processus détecterait, au moment de l'enregistrement d'un événement, que de nouveaux événements sont arrivés depuis qu'il a commencé à traiter la commande? Je suppose que cela nécessiterait un magasin d'événements qui prend en charge la sémantique "comparer et échanger", n'est-ce pas? (par exemple "écrire cet événement uniquement et uniquement si le dernier événement a l'ID X")?
Olivier Lalonde
2) J'aime aussi l'idée d'accepter des incohérences temporaires et de les réparer éventuellement, mais je ne sais pas comment je coderais cela de manière fiable ... peut-être avoir un processus dédié qui valide les événements séquentiellement et crée des événements de restauration lorsqu'il détecte quelque chose a mal tourné? Merci!
Olivier Lalonde
(1) Je dirais "nouvelle version de l'histoire" plutôt que "nouveaux événements", mais vous avez l'idée; ne remplacez l’histoire que si c’est celle que nous attendons.
VoiceOfUnreason
(2) Ouaip. C'est un peu de logique qui lit les événements du magasin par lots, et à la fin du lot diffuse un rapport d'exception ("nous avons trop d'utilisateurs nommés Bob"), ou envoie des commandes pour compenser le problème (en supposant que la bonne réponse est calculable sans intervention humaine).
VoiceOfUnreason
2

On dirait que vous pourriez implémenter un processus métier ( sagadans le contexte de Domain Driven Design) pour l'enregistrement de l'utilisateur où l'utilisateur est traité comme un CRDT.

Ressources

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

  2. "CRDT avec Akka Distributed Data" https://www.slideshare.net/markusjura/crdts-with-akka-distributed-data pour en savoir plus sur

    • CmRDTs - CRDT basés sur les opérations
    • CvRDTs - CRTD basés sur l'état
  3. Exemples de code dans Scala https://github.com/akka/akka-samples/tree/master/akka-sample-distributed-data-scala . Peut-être que "panier" est le plus approprié.

  4. Visite du cluster Akka - Akka Distributed Data https://manuel.bernhardt.io/2018/01/03/tour-akka-cluster-akka-distributed-data/
SemanticBeeng
la source