Guide des transactions entre microservices
1. introduction
Dans cet article, nous aborderons les options permettant de mettre en œuvre une transaction entre les microservices.
Nous allons également découvrir quelques alternatives aux transactions dans un scénario de microservice distribué.
2. Éviter les transactions entre les microservices
Une transaction distribuée est un processus très complexe comportant de nombreuses pièces mobiles susceptibles d’échouer. De plus, si ces pièces tournent sur des machines différentes ou même dans des centres de données différents, le processus de validation d'une transaction peut devenir très long et peu fiable.
Cela pourrait sérieusement affecter l'expérience utilisateur et la bande passante globale du système. Doncone of the best ways to solve the problem of distributed transactions is to avoid them completely.
2.1. Exemple d'architecture nécessitant des transactions
Habituellement, un microservice est conçu de manière à être indépendant et utile par lui-même. Il devrait être capable de résoudre une tâche atomique.
Si nous pouvions diviser notre système en de tels microservices, il y a de fortes chances que nous n'ayons pas du tout besoin de mettre en œuvre des transactions entre eux.
Par exemple, considérons un système de diffusion de messages entre utilisateurs.
Le microserviceuser serait concerné par le profil utilisateur (création d'un nouvel utilisateur, modification des données de profil, etc.) avec la classe de domaine sous-jacente suivante:
@Entity
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Basic
private String name;
@Basic
private String surname;
@Basic
private Instant lastMessageTime;
}
Le microservicemessage serait concerné par la diffusion. Il encapsule l'entitéMessage et tout ce qui l'entoure:
@Entity
public class Message implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Basic
private long userId;
@Basic
private String contents;
@Basic
private Instant messageTimestamp;
}
Chaque microservice a sa propre base de données. Notez que nous ne faisons pas référence à l'entitéUser de l'entitéMessage, car les classes d'utilisateurs ne sont pas accessibles depuis le microservicemessage. Nous nous référons à l'utilisateur uniquement par id.
Maintenant, l'entitéUser contient le champlastMessageTime car nous voulons afficher les informations sur la dernière heure d'activité de l'utilisateur dans son profil.
Cependant, pour ajouter un nouveau message à l'utilisateur et mettre à jour seslastMessageTime, nous devons maintenant implémenter une transaction sur les microservices.
2.2. Approche alternative sans transactions
Nous pouvons modifier notre architecture de microservice et supprimer le champlastMessageTime de l'entitéUser.
Ensuite, nous pourrions afficher cette heure dans le profil de l'utilisateur en émettant une requête distincte au microservice de messages et en trouvant la valeur maximale demessageTimestamp pour tous les messages de cet utilisateur.
Probablement, si le microservicemessage est soumis à une charge élevée ou même en panne, nous ne pourrons pas afficher l'heure du dernier message de l'utilisateur dans son profil.
Mais cela pourrait être plus acceptable que de ne pas valider une transaction distribuée pour enregistrer un message simplement parce que le microservice utilisateur n'a pas répondu à temps.
Il existe bien sûr des scénarios plus complexes lorsque nous devons implémenter un processus métier sur plusieurs microservices, et nous ne voulons pas permettre une incohérence entre ces microservices.
3. Protocole de validation en deux phases
Two-phase commit protocol (ou 2PC) est un mécanisme pour implémenter une transaction sur différents composants logiciels (bases de données multiples, files d'attente de messages, etc.)
3.1. L'architecture de 2PC
Le coordinateur des transactions est l’un des participants importants à une transaction distribuée. La transaction distribuée comprend deux étapes:
-
Phase de préparation - au cours de cette phase, tous les participants à la transaction se préparent pour la validation et informent le coordinateur qu'ils sont prêts à terminer la transaction.
-
Phase de validation ou d'annulation - Au cours de cette phase, le coordinateur de transaction envoie une commande de validation ou d'annulation à tous les participants.
Le problème avec 2PC est qu'il est assez lent par rapport au temps de fonctionnement d'un seul microservice.
Coordinating the transaction between microservices, even if they are on the same network, can really slow the system down, cette approche n'est donc généralement pas utilisée dans un scénario de charge élevée.
3.2. Norme XA
LeXA standard est une spécification pour effectuer les transactions distribuées 2PC à travers les ressources de support. Tout serveur d'applications compatible JTA (JBoss, GlassFish, etc.) le prend en charge immédiatement.
Les ressources participant à une transaction distribuée pourraient être, par exemple, deux bases de données de deux microservices différents.
Cependant, pour tirer parti de ce mécanisme, les ressources doivent être déployées sur une seule plateforme JTA. Ce n’est pas toujours possible pour une architecture de microservices.
3.3. Projet standard REST-AT
Une autre norme proposée estREST-AT, qui a subi un certain développement par RedHat mais qui n’est toujours pas sortie du stade de projet. Il est cependant pris en charge par le serveur d'applications WildFly prêt à l'emploi.
Cette norme permet d'utiliser le serveur d'applications en tant que coordinateur de transaction avec une API REST spécifique pour créer et joindre les transactions distribuées.
Les services Web RESTful souhaitant participer à la transaction en deux phases doivent également prendre en charge une API REST spécifique.
Malheureusement, pour relier une transaction distribuée aux ressources locales du microservice, nous devions encore soit déployer ces ressources sur une seule plate-forme JTA, soit résoudre nous-mêmes une tâche non triviale consistant à écrire ce pont.
4. Cohérence et compensation à terme
De loin, l'un des modèles les plus réalisables de gestion de la cohérence entre les microservices esteventual consistency.
Ce modèle n'applique pas les transactions ACID distribuées sur les microservices. Au lieu de cela, il propose d'utiliser certains mécanismes garantissant que le système sera éventuellement cohérent à l'avenir.
4.1. Un cas pour une cohérence éventuelle
Par exemple, supposons que nous devions résoudre la tâche suivante:
-
enregistrer un profil d'utilisateur
-
faire des vérifications d'arrière-plan automatisées que l'utilisateur peut réellement accéder au système
La deuxième tâche consiste à garantir, par exemple, que cet utilisateur n’a pas été banni de nos serveurs pour une raison quelconque.
Mais cela peut prendre du temps et nous aimerions l'extraire dans un microservice distinct. Il ne serait pas raisonnable de faire attendre l’utilisateur aussi longtemps pour savoir qu’il s’est bien enregistré.
One way to solve it would be with a message-driven approach including compensation. Considérons l'architecture suivante:
-
le microserviceuser chargé d'enregistrer un profil utilisateur
-
le microservicevalidation chargé de faire une vérification des antécédents
-
la plate-forme de messagerie qui prend en charge les files d'attente persistantes
La plate-forme de messagerie pourrait s’assurer que les messages envoyés par les microservices sont conservés. Ils seraient ensuite livrés ultérieurement si le récepteur n'était pas disponible actuellement
4.2. Scénario heureux
Dans cette architecture, un scénario heureux serait:
-
le microserviceuser enregistre un utilisateur et enregistre les informations le concernant dans sa base de données locale
-
le microserviceuser marque cet utilisateur avec un indicateur. Cela peut signifier que cet utilisateur n'a pas encore été validé et n'a pas accès à toutes les fonctionnalités du système
-
une confirmation de l'enregistrement est envoyée à l'utilisateur avec un avertissement que toutes les fonctionnalités du système ne sont pas immédiatement accessibles
-
le microserviceuser envoie un message au microservicevalidation pour faire la vérification des antécédents d'un utilisateur
-
le microservicevalidation exécute la vérification des antécédents et envoie un message au microserviceuser avec les résultats de la vérification
-
si les résultats sont positifs, le microserviceuser débloque l'utilisateur
-
si les résultats sont négatifs, le microserviceuser supprime le compte utilisateur
-
Après avoir suivi toutes ces étapes, le système doit être dans un état cohérent. Cependant, pendant un certain temps, l’entité utilisateur semblait être dans un état incomplet.
The last step, when the user microservice removes the invalid account, is a compensation phase.
4.3. Scénarios d'échec
Considérons maintenant quelques scénarios de panne:
-
si le microservice devalidation n'est pas accessible, la plateforme de messagerie avec sa fonctionnalité de file d'attente persistante garantit que le microservice devalidation recevra ce message ultérieurement
-
supposons que la plate-forme de messagerie échoue, puis le microserviceuser essaie de renvoyer le message ultérieurement, par exemple, en planifiant le traitement par lots de tous les utilisateurs non encore validés
-
si le microservicevalidation reçoit le message, valide l'utilisateur mais ne peut pas renvoyer la réponse en raison de l'échec de la plate-forme de messagerie, le microservicevalidation essaie également d'envoyer le message plus tard
-
si l'un des messages a été perdu ou si un autre échec s'est produit, le microserviceuser trouve tous les utilisateurs non validés par un traitement par lots planifié et envoie à nouveau des demandes de validation
Même si certains des messages étaient émis plusieurs fois, cela n'affecterait pas la cohérence des données dans les bases de données des microservices.
En examinant attentivement tous les scénarios de défaillance possibles, nous pouvons nous assurer que notre système satisferait aux conditions d'une éventuelle cohérence. Dans le même temps, nous n’aurions pas besoin de gérer les transactions distribuées coûteuses.
Mais nous devons être conscients que la cohérence éventuelle est une tâche complexe. Il n’a pas de solution unique pour tous les cas.
5. Conclusion
Dans cet article, nous avons abordé certains des mécanismes de mise en œuvre des transactions entre les microservices.
Et, nous avons également exploré quelques alternatives pour faire ce style de transactions en premier lieu.