Un guide des transactions à travers les microservices

1. Introduction

Dans cet article, nous discuterons des options pour implémenter une transaction sur les microservices.

Nous allons également vérifier 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 avec de nombreuses pièces mobiles qui peuvent échouer. De plus, si ces pièces fonctionnent sur différentes machines ou même dans différents centres de données, 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. Donc, l' un des meilleurs moyens de résoudre le problème des transactions distribuées est de les éviter complètement.

2.1. Exemple d'architecture nécessitant des transactions

Habituellement, un microservice est conçu de manière à être indépendant et utile en soi. Il devrait être capable de résoudre une tâche commerciale 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 messagerie de diffusion entre utilisateurs.

Le microservice utilisateur 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 microservice de messages concernerait la diffusion. Il encapsule le message d' entité 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 possède sa propre base de données. Notez que nous ne faisons pas référence à l'entité User à partir de l'entité Message , car les classes d'utilisateurs ne sont pas accessibles à partir du microservice de message . Nous nous référons à l'utilisateur uniquement par identifiant.

Désormais, l' entité User contient le champ lastMessageTime car nous souhaitons 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 son lastMessageTime , nous devrons maintenant implémenter une transaction sur les microservices.

2.2. Approche alternative sans transactions

Nous pouvons modifier notre architecture de microservice et supprimer le champ lastMessageTime de l' entité User .

Ensuite, nous pourrions afficher cette heure dans le profil de l'utilisateur en émettant une demande distincte au microservice de messages et en recherchant la valeur messageTimestamp maximale pour tous les messages de cet utilisateur.

Probablement, si le microservice de message est sous 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

Le protocole de validation en deux phases (ou 2PC) est un mécanisme permettant d'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

L'un des participants importants à une transaction distribuée est le coordinateur de transaction. La transaction distribuée se compose de deux étapes:

  • Phase de préparation - au cours de cette phase, tous les participants de la transaction se préparent à la validation et notifient au coordinateur qu'ils sont prêts à terminer la transaction
  • Phase de validation ou de restauration - au cours de cette phase, une commande de validation ou de restauration est émise par le coordinateur de transaction à tous les participants

Le problème avec 2PC est qu'il est assez lent par rapport au temps de fonctionnement d'un seul microservice.

La coordination de la transaction entre les microservices, même s'ils sont sur le même réseau, peut vraiment ralentir le système , donc cette approche n'est généralement pas utilisée dans un scénario de charge élevée.

3.2. Norme XA

La norme XA est une spécification pour la conduite des transactions distribuées 2PC à travers les ressources de support. Tout serveur d'application 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 est REST-AT qui a subi un certain développement par RedHat mais 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'application comme coordinateur de transactions avec une API REST spécifique pour créer et joindre les transactions distribuées.

Les services Web RESTful qui souhaitent 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 devrons toujours soit déployer ces ressources sur une seule plate-forme JTA, soit résoudre nous-mêmes une tâche non triviale d'écriture de ce pont.

4. Cohérence et compensation à terme

By far, one of the most feasible models of handling consistency across microservices is eventual consistency.

This model doesn't enforce distributed ACID transactions across microservices. Instead, it proposes to use some mechanisms of ensuring that the system would be eventually consistent at some point in the future.

4.1. A Case for Eventual Consistency

For example, suppose we need to solve the following task:

  • register a user profile
  • do some automated background check that the user can actually access the system

The second task is to ensure, for example, that this user wasn't banned from our servers for some reason.

But it could take time, and we'd like to extract it to a separate microservice. It wouldn't be reasonable to keep the user waiting for so long just to know that she was registered successfully.

One way to solve it would be with a message-driven approach including compensation. Let's consider the following architecture:

  • the user microservice tasked with registering a user profile
  • the validation microservice tasked with doing a background check
  • the messaging platform that supports persistent queues

The messaging platform could ensure that the messages sent by the microservices are persisted. Then they would be delivered at a later time if the receiver weren't currently available

4.2. Happy Scenario

In this architecture, a happy scenario would be:

  • the user microservice registers a user, saving information about her in its local database
  • the user microservice marks this user with a flag. It could signify that this user hasn't yet been validated and doesn't have access to full system functionality
  • a confirmation of registration is sent to the user with a warning that not all functionality of the system is accessible right away
  • the user microservice sends a message to the validation microservice to do the background check of a user
  • the validation microservice runs the background check and sends a message to the user microservice with the results of the check
    • if the results are positive, the user microservice unblocks the user
    • if the results are negative, the user microservice deletes the user account

After we've gone through all these steps, the system should be in a consistent state. However, for some period of time, the user entity appeared to be in an incomplete state.

The last step, when the user microservice removes the invalid account, is a compensation phase.

4.3. Failure Scenarios

Now let's consider some failure scenarios:

  • if the validation microservice is not accessible, then the messaging platform with its persistent queue functionality ensures that the validation microservice would receive this message at some later time
  • suppose the messaging platform fails, then the user microservice tries to send the message again at some later time, for example, by scheduled batch-processing of all users that were not yet validated
  • if the validation microservice receives the message, validates the user but can't send the answer back due to the messaging platform failure, the validation microservice also retries sending the message at some later time
  • if one of the messages got lost, or some other failure happened, the user microservice finds all non-validated users by scheduled batch-processing and sends requests for validation again

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 discuté de certains des mécanismes d'implémentation des transactions entre les microservices.

Et, nous avons également exploré quelques alternatives pour faire ce style de transactions en premier lieu.