Aujourd'hui nous portons à votre attention un petit matériel sur les microservices et l'architecture distribuée. En particulier, il touche à l'idée de Martin Fowler qu'un nouveau système devrait partir d'un monolithe, et même dans une architecture de microservice développée, il est conseillé de laisser un grand noyau monolithique.
Bonne lecture!
Aujourd'hui, tout le monde pense aux microservices et les écrit - et je ne fais pas exception. Sur la base des principes de base des microservices et de leur véritable contexte, il est clair que les microservices sont un système distribué.
Qu'est-ce qu'une transaction distribuée?
Les transactions qui couvrent plusieurs systèmes physiques ou ordinateurs sur un réseau sont simplement appelées transactions distribuées. Dans le monde des microservices, une transaction est divisée en plusieurs services qui sont appelés dans une séquence pour terminer la transaction entière.
Voici un système de boutique en ligne monolithique qui utilise des transactions:
Fig. 1: Une transaction dans le monolithe
Si dans le système ci-dessus, un utilisateur envoie une requête à la plateforme sur la commande (Checkout), la plateforme crée une transaction locale dans la base de données, et cette transaction couvre une multitude de tables de base de données à traiter (Process) et carnet de commandes(Réserve) marchandises de l'entrepôt. Si l'une de ces étapes échoue, la transaction peut être annulée , ce qui signifie le refus de la commande elle-même et des marchandises réservées. Cet ensemble de principes est appelé ACID (atomicité, cohérence, isolation, durabilité) et est garanti au niveau du système de base de données.
Voici une décomposition d'un système de boutique en ligne construit à partir de microservices:
Figure 2: Transactions dans un microservice
Après avoir décomposé ce système, nous avons créé des microservices
OrderMicroservice
etInventoryMicroservice
avec des bases de données séparées. Lorsqu'une demande de retrait provient d'un utilisateur, ces deux microservices sont appelés et chacun d'eux apporte des modifications à sa base de données. Puisqu'une transaction est maintenant répartie sur plusieurs bases de données sur plusieurs systèmes, elle est considérée comme distribuée .
Quel est le problème lors de la validation des transactions distribuées dans les microservices?
Avec l'introduction de l'architecture de microservices, les bases de données perdent leur nature ACID. En raison de la prolifération possible des transactions entre de nombreux microservices et donc des bases de données, les problèmes clés suivants doivent être traités:
Comment maintenir l'atomicité des transactions?
L'atomicité signifie que dans toute transaction, soit toutes les étapes peuvent être terminées, soit aucune. Si l'exemple ci-dessus ne parvient pas à terminer l'opération «articles de commande» dans la méthode
InventoryMicroservice
, comment annuler les modifications du «traitement de commande» qui ont été appliquées OrderMicroservice
?
Comment gérer les demandes concurrentielles?
Supposons qu'un objet de l'un des microservices entre dans la base de données pour un stockage à long terme, et qu'en même temps une autre demande lit le même objet. Quelles données le service doit-il renvoyer - anciennes ou nouvelles? Dans l'exemple ci-dessus, lorsqu'il
OrderMicroservice
a déjà terminé le travail et InventoryMicroservice
est en cours de mise à jour, devez-vous inclure la commande en cours dans le nombre de demandes de commandes passées par l'utilisateur?
Les systèmes modernes sont conçus en gardant à l'esprit les défaillances potentielles, et l'un des problèmes majeurs du traitement des transactions distribuées est bien exposé par Pat Helland.
En règle générale, les développeurs ne créent tout simplement pas de grandes applications évolutives qui impliqueraient de travailler avec des transactions distribuées.
Solutions possibles
Les deux problèmes ci-dessus sont très critiques dans le contexte de la conception et de la création d'applications basées sur des microservices. Pour les résoudre, les deux approches suivantes sont utilisées:
- Fixation en deux phases
- Cohérence et compensation ultimes / SAGA
1. Fixation en deux phases
Comme son nom l'indique, cette méthode de traitement des transactions comporte deux étapes: une phase de préparation et une phase de validation. Un rôle important dans ce cas est joué par le coordinateur de la transaction, organisant le cycle de vie de la transaction.
Comment ça fonctionne
Au stade préparatoire, tous les microservices participant au travail se préparent à la validation et notifient au coordinateur qu'ils sont prêts à terminer la transaction. Ensuite, à l'étape suivante, une validation se produit ou le coordinateur de transactions demande à tous les microservices de revenir en arrière.
Considérez à nouveau un système de boutique en ligne comme exemple:
Figure 3: validation réussie en deux phases dans un système de microservices
Dans l'exemple ci-dessus (Figure 3), lorsqu'un utilisateur soumet une demande de commande, le coordinateur
TransactionCoordinator
commence d'abord une transaction globale avec des informations de contexte complètes. Tout d'abord, il envoie la commande prepare au microservice OrderMicroservice
pour créer la commande. Ensuite, il envoie la commande de préparation àInventoryMicroservice
pour réserver des articles. Lorsque les deux services sont prêts à apporter des modifications, ils bloquent les objets des modifications ultérieures et en informent TransactionCoordinator
. Une fois qu'il TransactionCoordinator
confirme que tous les microservices sont prêts à appliquer leurs modifications, il ordonnera à ces microservices de les enregistrer en demandant une validation de la transaction. À ce stade, tous les objets seront déverrouillés.
Figure 4: Echec de la validation en deux phases lors de l'utilisation de microservices
Dans un scénario d'échec (Figure 4) - si à tout moment un microservice n'a pas le temps de se préparer,
TransactionCoordinator
annulez la transaction et commencez le processus de restauration. Sur le diagramme, OrderMicroservice
pour une raison quelconque, je n'ai pas pu créer de commande, mais InventoryMicroservice
j'ai répondu que j'étais prêt à créer une commande. Le coordinateur TransactionCoordinator
demandera une annulation àInventoryMicroservice
, après quoi le service annulera toutes les modifications apportées et déverrouillera les objets de la base de données.
Avantages
- Cette approche garantit l'atomicité de la transaction. La transaction se terminera lorsque les deux microservices réussissent ou lorsque les microservices n'apportent aucune modification.
- Deuxièmement, cette approche vous permet d'isoler la lecture de l'écriture, car les modifications apportées aux objets ne sont pas visibles tant que le coordinateur de transaction n'a pas validé ces modifications.
- Cette approche est un appel synchrone dans lequel le client sera informé du succès ou de l'échec.
désavantages
- Rien n'est parfait; Les validations en deux phases sont plutôt lentes par rapport aux opérations de microservice uniques. Ils dépendent fortement du coordinateur. transactions, ce qui peut ralentir considérablement le système pendant les périodes de forte charge.
- Un autre inconvénient majeur est le verrouillage des lignes de la base de données. Le verrouillage peut devenir un goulot d'étranglement des performances et des blocages peuvent se produire , où deux transactions se verrouillent étroitement.
2. Cohérence et compensation ultimes / SAGA
L'une des meilleures définitions de la cohérence est finalement donnée sur microservices.io: chaque service publie un événement chaque fois que ses données sont mises à jour. D'autres services s'abonnent à des événements. Lorsqu'un événement est reçu, le service met à jour ses données .
Avec cette approche, une transaction distribuée est exécutée en tant que collection de transactions locales asynchrones sur les microservices correspondants. Les microservices échangent des informations via le bus d'événements.
Comment ça fonctionne
Encore une fois, prenons un exemple de système fonctionnant dans une boutique en ligne:
Figure 5: Ultimate Consistency / SAGA, Success
Dans l'exemple ci-dessus (Figure 5), le client demande au système de traiter la commande. Cette demande
Choreographer
déclenche l'événement Create Order, qui démarre la transaction. Le microservice OrderMicroservice
écoute cet événement et crée une commande - si cette opération réussit, il déclenche l'événement Order Created. Le coordinateur Choreographer
écoute cet événement et procède à la commande d'articles, ce qui déclenche l'événement de réserve d'articles. MicroserviceInventoryMicroservice
écoute cet événement et commande des marchandises; si cet événement réussit, il déclenche l'événement Items Reserved. Dans cet exemple, cela signifie que la transaction est terminée.
Toutes les communications événementielles entre microservices se font via le bus d'événements, et un autre système est responsable de son organisation (chorégraphie) - c'est ainsi que le problème est résolu avec une complexité inutile.
Figure 6: Cohérence ultime / SAGA, résultat d' échec
Si, pour une raison quelconque, les
InventoryMicroservice
articles n'ont pas été réservés (Figure 6), cela déclenche l'événement Échec de la réservation des articles. Le coordinateur Choreographer
écoute cet événement et démarre la transaction de compensation, en déclenchant l'événement Supprimer la commande. MicroserviceOrderMicroservice
écoute cet événement et supprime la commande précédemment créée.
Avantages
Un avantage majeur de cette approche est que chaque microservice se concentre uniquement sur sa propre transaction atomique. Les microservices ne sont pas bloqués si un autre service prend un temps relativement long à s'exécuter. Cela signifie également que vous n'avez pas non plus besoin de verrouiller la base de données. Avec cette approche, il est possible d'assurer une bonne évolutivité du système lors de travaux sous forte charge, puisque la solution proposée est asynchrone et basée sur le travail avec des événements.
désavantages
Le principal inconvénient de cette approche est qu'elle ne fournit pas d'isolation en lecture. Ainsi, dans l'exemple ci-dessus, le client verra que la commande a été créée, mais après une seconde la commande sera supprimée lors de la transaction de compensation. De plus, à mesure que le nombre de microservices augmente, il devient plus difficile de les déboguer et de les maintenir.
Conclusion
La première alternative à l'approche proposée consiste à abandonner complètement les transactions distribuées. Si vous créez une nouvelle application, commencez par une architecture monolithique, comme décrit dans MonolithFirst de Martin Fowler. Je vais le citer.
, , . , , . —Si vous avez besoin de mettre à jour des données à deux endroits à la fois à la suite d'un seul événement, alors l'approche de cohérence finale / SAGA est préférable pour le traitement des transactions distribuées par rapport à l'approche en deux phases. La raison principale est que l'approche en deux phases dans un environnement distribué ne s'adapte pas. L'utilisation de la cohérence soulève également finalement son propre ensemble de problèmes, tels que la mise à jour atomique de la base de données et le déclenchement d'un événement. Passant à une telle philosophie de développement, il est nécessaire de changer sa perception à la fois du point de vue du développeur et du point de vue du testeur.