Microservices: gestion de la cohérence éventuelle

22

Supposons que nous ayons une fonction qui met à jour le mot de passe d'un utilisateur.

Une fois que le bouton «Mettre à jour le mot de passe» est cliqué, un événement UpdatePasswordEvent est envoyé à une rubrique où 3 autres services sont abonnés:

  1. Un service qui met à jour le mot de passe de l'utilisateur
  2. Un service qui met à jour l'historique des mots de passe de l'utilisateur
  3. Un service qui envoie un e-mail informant l'utilisateur que son mot de passe a été modifié.

D'après ce que j'ai compris de la cohérence éventuelle, tous ces services (consommateurs) recevront l'événement en même temps et les traiteront séparément, ce qui, dans un bon scénario, conduira à la cohérence des données.

Cependant, que se passe-t-il si un service ne parvient pas à traiter l'événement? par exemple, déconnexion soudaine, erreur de base de données, etc. Quel est un bon modèle / pratique pour gérer ces échecs de transaction?

Je pensais créer un RollbackTopic où si un événement ne pouvait pas être traité, un RollbackEvent serait créé dans une rubrique où les "services de restauration" feraient leur travail et rétabliraient les données

mpmp
la source
11
Vous ne pouvez pas annuler un e-mail envoyé :-)
Laiv
2
Parce que tous devraient faire partie du même service. Les micro-services sont opposés aux monolithes, cela ne signifie pas que vous devez les concevoir aussi peu que «physiquement» possible. Bien que cela ne soit pas directement lié, vous devriez lire cette question et les deux principales réponses: softwareengineering.stackexchange.com/questions/339230/…
Walfrat
1
Vous souhaiterez peut-être envisager de mettre à jour le mot de passe de l'utilisateur dans la base de données de manière synchrone, afin de fournir un retour immédiat à l'utilisateur, et de déclencher d'autres services de manière asynchrone en émettant un message indiquant que le mot de passe a changé sur un sujet, afin que votre message n'ait pas à contenir le mot de passe.
cr3
L'e-mail indique-t-il à l'utilisateur que la transaction est terminée ou est-il là pour indiquer à l'utilisateur que quelqu'un (espérons-le) a changé le mot de passe? "Si ce n'était pas vous, alors vous devez agir". Si le 2e envoie simplement un e-mail maintenant, du mieux que vous pouvez.
ctrl-alt-delor

Réponses:

29

Sur la base de ce que j'ai compris sur la cohérence éventuelle, tous ces services (consommateurs) recevront l'événement en même temps et les traiteront séparément, ce qui, dans un bon scénario, conduira à la cohérence des données.

Non pas forcément. Comme je l'ai commenté, nous ne pouvons pas annuler un e-mail envoyé, nous avons donc encore besoin d'une sorte de "séquence". IPC sur gestion des données événementiel n'est pas exempte d'orchestation 1 .

Par exemple, l'e-mail ne doit pas être envoyé à moins que les transactions précédentes ne se terminent avec succès et que le service de messagerie n'en obtienne une preuve. 3

Cependant, que se passe-t-il si un service ne parvient pas à traiter l'événement? par exemple, déconnexion soudaine, erreur de base de données, etc. Quel est un bon modèle / pratique pour gérer ces échecs de transaction?

Dites bonjour aux erreurs de l'informatique distribuée . Ce sont eux qui compliquent les choses et, comme d'habitude, il n'y a pas de balles d'argent pour y faire face.

Avant de commencer notre voyage à la recherche de l'Arche perdue, nous devons d'abord demander à l'organisation. Souvent, la solution réside dans la façon dont l'organisation fait face à ces problèmes dans le monde réel .

Que fait tout le monde (départements) lorsque certaines données sont manquantes ou incomplètes?

Nous nous rendrons compte que les différents services ont des solutions différentes qui, ensemble, constituent la solution à mettre en œuvre.

Quoi qu'il en soit, voici quelques pratiques qui pourraient nous aider dans la stratégie à suivre.

Éventuelle cohérence

Plutôt que de veiller à ce que le système soit constamment dans un état cohérent, nous pouvons plutôt accepter que le système l'obtiendra à un moment donné dans le futur. Cette approche est particulièrement utile pour les opérations commerciales de longue durée.

La façon dont le système atteint la cohérence varie d'un système à l'autre. Cela peut impliquer des processus automatisés à une sorte d'intervention humaine. Par exemple, le réessayer typique plus tard ou le contact avec le service client .

Annuler toutes les opérations

Remettez le système dans un état cohérent via des transactions compensatoires . Cependant, nous devons tenir compte du fait que ces transactions peuvent également échouer, ce qui pourrait nous conduire à un point où l'incohérence est encore plus difficile à résoudre. Et, encore une fois, nous ne pouvons pas annuler un e-mail envoyé.

Pour un faible nombre de transactions, cette approche est faisable, car le nombre de transactions compensatoires est également faible. S'il y avait plusieurs transactions commerciales impliquées dans la CIB, le traitement d'une transaction compensatoire pour chacune d'entre elles serait difficile.

Si nous optons pour des transactions compensatoires , nous trouverons que le modèle de conception des disjoncteurs est très utile - et obligatoire j'oserais dire -

Transactions distribuées

L'idée est de couvrir plusieurs transactions au sein d'une même transaction, via un processus de gouvernance global appelé Transaction Manager . Un algorithme courant pour gérer les transactions distribuées est la validation en deux phases .

La principale préoccupation des transactions distribuées est qu'elles reposent sur le verrouillage des ressources pendant sa durée de vie, et comme nous le savons, les choses peuvent mal tourner pour le gestionnaire de transactions .

Si les gestionnaires de transactions sont compromis, nous pouvons nous retrouver avec plusieurs verrous dans les différents contextes limités, ce qui entraîne des comportements inattendus en raison de la mise en file d'attente des messages. 2

Opérations de décomposition. Pourquoi?

Si vous décomposez un système existant et que vous trouvez une collection de concepts qui veulent vraiment être dans une seule frontière de transaction, laissez-les peut-être pour la fin.

Sam Newman

En accord avec les arguments ci-dessus, Sam - dans son livre Building Microservices - déclare que, si nous ne pouvons vraiment pas vraiment nous permettre la cohérence éventuelle, nous devons éviter de diviser l'opération maintenant.

Si nous ne pouvons pas nous permettre de scinder certaines opérations en deux ou plusieurs transactions, cela pourrait dire que - probablement - ces transactions appartiennent au même contexte délimité, ou - au moins - à un contexte transversal qui reste à modéliser.

Par exemple, dans notre cas, nous nous rendons compte que les transactions # 1 et # 2 sont étroitement liées l'une à l'autre et probablement les deux pourraient appartenir au même contexte délimité Comptes , Utilisateurs , Registre , peu importe ...

Pensez à placer les deux opérations dans les limites de la même transaction. Cela rendrait l'opération plus facile à gérer. Évaluez également le niveau de criticité de chaque transaction. Probablement, si la transaction # 2 échoue, elle ne devrait pas compromettre toute l'opération. En cas de doute, demandez à l' organisation .


1: Pas le genre d'orchestration que vous pensez. Je ne parle pas de l'orchestation d'ESB. Je parle de faire réagir les services à l'événement approprié.

2: Vous pourriez trouver les opinions intéressantes de Sam Newman concernant les transactions distribuées.

3: Consultez la réponse de David Parker à ce sujet.

Laiv
la source
3
Très bonne réponse. Je voudrais seulement souligner l'importance de prendre en compte les risques qui surviennent lors de l'utilisation de transactions distribuées - principalement le verrouillage des ressources produisant des blocages et des arrêts de systèmes. Sur un produit de commerce électronique sur lequel j'ai travaillé il y a environ 3 ans, nous avons dû remplacer les DT par un système de messagerie, car avec le nombre d'utilisateurs disponibles dans les systèmes, le système était très sujet aux erreurs. Les problèmes avec les DT se produisent principalement lorsqu'une base d'utilisateurs augmente.
Andy
7

Dans votre cas, vous ne pouvez pas traiter les trois choses à la fois. Ce dont vous avez besoin, c'est d'un processus. Voici un exemple extrêmement simplifié:

Orchestration de commandes et d'événements

Il est important de savoir que les opérations de modification d'état DOIVENT toujours être effectuées sur une entité cohérente. Sauf si vous pouvez garantir une cohérence élevée , elle doit être faite sur un dossier principal.

Votre système doit garantir qu'avant qu'un événement ne se produise dans votre système, les modifications DOIVENT d'abord être conservées avec la sécurité transactionnelle. C'est pour s'assurer qu'un événement déclenché est vraiment une confirmation de ce qui s'est réellement passé.

Il y a plusieurs parties délicates du processus tel quel et je vais ignorer les plus évidentes - telles que: Que faire si votre serveur de base de données meurt lorsqu'il persiste un utilisateur avec un mot de passe modifié? Vous émettez simplement le UpdatePassword à nouveau. Cependant, certaines parties doivent être prises en charge par vous, et ce sont:

  • gérer la duplication des messages,
  • gérer l'envoi d'e-mails.

Dans un système, l'orchestrateur de processus (PO) n'est rien d'autre qu'une autre entité, qui contient un état interne - au sens littéral également - et permet des transitions entre les états, agissant effectivement comme une sorte de machine à états. Grâce à l'état interne, vous pouvez supprimer le traitement de duplication des messages.

Lorsque le bon de commande est dans un Newétat et est en cours de traitement UserPasswordHasBeenUpdated, il change son état en UserPasswordHasBeenUpdated(ou selon le nom d'état qui vous convient ). Si le bon de commande se trouve toujours dans un UserPasswordHasBeenUpdatedet un autreUserPasswordHasBeenUpdated arriverait, le bon de commande ignorerait complètement le message, sachant qu'il s'agit d'une duplication. Un mécanisme similaire serait également mis en œuvre pour d'autres États.

La gestion de l'envoi réel de l'e-mail est un peu plus délicate. Ici, vous avez deux options:

  1. envoyez-le au plus une fois,
  2. envoyez-le au moins une fois.

Envoyez-le au plus une fois

Avec cette option, lorsque le bon de UserPasswordHistoryHasBeenSavedcommande a atteint l' état, une commande pour envoyer un e-mail est envoyée en réaction au changement d'état. Votre système garantirait la UserPasswordHistoryHasBeenSavedpersistance de l' état avant d'envoyer l'e-mail, c'est-à-dire qu'un message en double ne déclencherait pas l'envoi de l'e-mail à nouveau. Avec cette approche, vous vous assurez que l'état correct est enregistré pour le bon de commande mais ne pouvez garantir aucune opération suivante.

Envoyez-le au moins une fois

C'est ce que j'irais.

Au lieu d'enregistrer UserPasswordHistoryHasBeenSavedet d'envoyer l'e-mail en réaction, vous essayez d'abord d'envoyer l'e-mail. Si l'opération d'envoi échoue, l'état du bon de commande n'est jamais changé en UserPasswordHistoryHasBeenSavedet un autre message du même type est toujours traité. Si l'envoi de l'e-mail devait réussir, mais que votre système échouerait pendant la persistance du bon de commande avec son nouvel UserPasswordHistoryHasBeenSavedétat, un autre message duUserPasswordHistoryHasBeenSaved déclencherait à nouveau la commande pour envoyer l'e-mail et l'utilisateur l'aurait reçu plusieurs fois .

Dans votre cas, vous voulez vous assurer que l'utilisateur reçoit bien l'e-mail. C'est pourquoi je choisirais la deuxième option par rapport à la première.

Andy
la source
2

Les systèmes de file d'attente ne sont pas aussi fragiles que vous ne le pensez.

Si nous écrivions les trois processus dans une base de données relationnelle, nous pourrions utiliser une transaction pour gérer une défaillance des processus intermédiaires.

Sans l'engagement final, le travail partiel serait rejeté.

Dans un système de bases de files d'attente, vous aurez des options similaires lorsque vous lirez un message dans la file d'attente pour gérer les échecs de mi-processus.

Amazon SQS, par exemple, masque simplement les messages qui sont lus. sauf si une dernière commande Supprimer est envoyée, le message réapparaît ou est placé dans une file d'attente de lettres mortes.

Vous pouvez implémenter des «transactions» similaires de différentes manières, essentiellement en conservant une copie du message jusqu'à ce que vous receviez la confirmation du succès du traitement. Si la confirmation n'est pas reçue à temps. vous pouvez renvoyer le message ou le conserver pour une attention manuelle.

Vous pourriez potentiellement créer un «service de restauration» qui surveillait ces messages erronés, connaissait les messages associés et l'état passé et effectuait une restauration.

Toutefois! Il est généralement préférable de renvoyer les messages erronés. Après tout, ce sont des cas marginaux. Soit un serveur a échoué de façon catastrophique, soit il y a eu un bogue dans la gestion d'un type de message particulier.

Une fois alerté de l'erreur, le service peut être réparé et les messages traités avec succès. Ramener le système dans son ensemble à un état cohérent.

Ewan
la source
2

Ce à quoi vous êtes confronté ici, c'est le problème des deux généraux . Essentiellement: comment être sûr qu'un message est reçu et qu'une réponse à ce message se produit? Dans de nombreux cas, une solution parfaite n'existe pas. En fait, dans un système distribué, il est souvent impossible d'obtenir une livraison exacte des messages.

Une première remarque évidente est que le service qui modifie le mot de passe doit envoyer l'événement de changement de mot de passe. De cette façon, l'historique des mots de passe et les services d'envoi de courrier ne sont déclenchés que lorsque le mot de passe change réellement, quelle que soit la raison pour laquelle il a changé.

Pour réellement résoudre votre problème, je ne considérerais pas les transactions distribuées, mais plutôt regarder dans le sens de la livraison de messages au moins une fois et du traitement idempotent.

  • Au moins une fois

    Pour vous assurer que l'événement de changement de mot de passe est réellement vu par tous les consommateurs, vous devez utiliser un canal de communication durable où les messages peuvent être consommés dans un style "au moins une fois". Les consommateurs reconnaissent un message comme consommé uniquement lorsqu'ils l'ont entièrement traité. Si, par exemple, le service d'historique des mots de passe se bloque lors de l'écriture d'une entrée d'historique, il relira le même événement de changement de mot de passe après le redémarrage et réessayera, reconnaissant cet événement comme étant en lecture seule après avoir été lui-même écrit dans l'historique. Vous devez choisir une solution de file d'attente de messages en fonction de sa capacité à renvoyer des messages jusqu'à ce qu'ils soient reconnus.

  • Idempotence

    Une fois la livraison effectuée au moins une fois, le problème est que des actions en double se produisent lorsqu'un message a été partiellement traité avant que le consommateur ne soit interrompu, puis retraité ultérieurement. Cela devrait être résolu en concevant chaque service afin qu'il soit idempotent. Soit les écritures qu'il effectue peuvent se produire plusieurs fois sans effets indésirables, soit il conserve sa propre mémoire des actions qu'il a effectuées et évite d'exécuter une action plus d'une fois. Dans le cas de l'envoi de courrier, vous constaterez que cela ne vaut probablement pas la peine d'essayer de le faire se comporter de manière idempotente et que tout va bien avec un courrier envoyé deux fois de temps en temps.

Dans tous les cas, faites attention à la façon dont vous effectuez vos services. Votre service d'historique de mot de passe doit-il vraiment être indépendant du service de changement de mot de passe?

Joeri Sebrechts
la source
1

Je suis en désaccord avec beaucoup de réponses.

  1. Envoyez l'e-mail maintenant «Quelqu'un a changé votre mot de passe. Si c'était vous, vous n'avez rien à faire. Sinon paniquez. »Cela arrivera quand il arrivera.
  2. Modifiez le mot de passe. Bien que vous ayez éventuellement une cohérence. Vous voulez vous assurer que cette session voit les modifications apportées par l'utilisateur.

Il y a d'autres promesses de cohérence que vous pouvez ajouter.

  • Assurez-vous que les changements se produisent dans l'ordre chronologique.
  • Assurez-vous qu'un utilisateur ne voit jamais de restauration, mais que d'autres utilisateurs ne voient toujours pas la modification.
  • Il y en a d'autres

Ces consistances supplémentaires devront être mises en œuvre en fonction des actes de la demande.


Je n'ai aucune idée de ce que vous entendez par «met à jour l'historique», mais veuillez ne jamais modifier l'historique. Si vous étendez simplement le DAG, cela devrait entraîner la modification de l'état actuel. Ils ne sont pas indépendants. S'ils le sont, vous ne pouvez pas vous fier à l'histoire qui reflète ce qui s'est passé. (et enfin et surtout, ne stockez pas les mots de passe voir comment ne pas stocker les mots de passe )

ctrl-alt-delor
la source
Si vous pouvez envoyer l'e-mail au début, votre approche est parfaite. Si vous devez envoyer quelque chose avec l'e-mail. Peut-être une sorte de lien / données qui ne peuvent être obtenues qu'une fois la cohérence atteinte, vous ne pouvez pas envoyer l'e-mail en premier. C'est ce que j'ai commenté consider asking the organization first.. Vous avez probablement raison. Cependant, j'ai trouvé important de conditionner ces événements que nous ne pouvons pas annuler. Par exemple, des notifications à l'utilisateur final. Une notification reposant sur l'état réel des données de l'utilisateur peut entraîner une mauvaise impression.
Laiv
Cela dit, pour ce scénario spécifique (notification de changement de mot de passe), j'ai accepté cette approche. Dès qu'il répond aux exigences.
Laiv