Quelle est la stratégie de transaction la plus acceptée pour microservices


80

L’un des problèmes majeurs que j’ai vu se produire dans un système doté de microservices est la façon dont les transactions fonctionnent lorsqu’elles couvrent différents services. Dans notre propre architecture, nous avons utilisé des transactions distribuées pour résoudre ce problème, mais elles ont leurs propres problèmes. Les impasses ont été particulièrement pénibles jusqu'à présent.

Une autre option semble être une sorte de gestionnaire de transactions sur mesure, qui connaît les flux au sein de votre système et se chargera des annulations pour vous en tant que processus d'arrière-plan couvrant l'ensemble de votre système (il demandera donc aux autres services d'annuler et s’ils sont en panne, informez-les plus tard).

Y at-il une autre option acceptée? Les deux semblent avoir leurs inconvénients. Le premier risque de provoquer des blocages et d’autres problèmes, le second risque d’entraîner une incohérence dans les données. Y a-t-il de meilleures options?


Juste pour être sûr, ces microservices sont-ils utilisés par plusieurs clients en même temps, ou juste un à la fois?
Marcel

2
J'essayais de poser cette question agnostique à cela, Marcel. Mais supposons que nous utilisons un système assez gros pour faire les deux, et nous voulons avoir une architecture qui les supporte.
Kristof

4
C'est une question mal formulée, mais très importante. Lorsque la plupart des gens pensent aux "transactions", ils pensent presque exclusivement à la cohérence et non aux aspects d'atomicité, d'isolation ou de durabilité des transactions. La question devrait en fait être "Comment créer un système compatible ACID avec une architecture de microservices. Implémenter uniquement la cohérence et non le reste d'ACID n'est pas vraiment utile. À moins que vous n'aimiez les mauvaises données.
Jeff Fischer

10
Vous pouvez toujours essayer de modifier ma question pour la rendre moins "mal formulée", Jeff Fischer.
Kristof

Cette question et cette discussion sont étroitement liées à cette autre question sur les SO .
Paulo Merson

Réponses:


42

L’approche habituelle consiste à isoler autant que possible ces microservices - à les traiter comme des unités uniques. Les transactions peuvent ensuite être développées dans le contexte du service dans son ensemble (c’est-à-dire qu’elles ne font pas partie des transactions de base de données habituelles, bien que vous puissiez toujours avoir des transactions de base de données internes au service).

Pensez à la manière dont les transactions se produisent et à la nature qui convient à vos services. Vous pouvez alors mettre en œuvre un mécanisme de restauration qui annule l’opération initiale ou un système de validation en deux phases qui réserve l’opération initiale jusqu’à ce que vous vous engagiez réellement. Bien sûr, ces deux systèmes signifient que vous implémentez le vôtre, mais vous implémentez déjà vos microservices.

Les services financiers font ce genre de choses tout le temps - si je veux transférer de l'argent de ma banque à votre banque, il n'y a pas de transaction unique comme dans un DB. Vous ne savez pas quels systèmes sont exploités par l'une ou l'autre banque, vous devez donc les traiter comme vos microservices. Dans ce cas, ma banque transférerait mon argent de mon compte vers un compte de dépôt, puis informerait votre banque qu’elle avait de l’argent. Si cet envoi échoue, elle me remboursera mon compte avec l’argent qu’elle a essayé d’envoyer.


5
Vous supposez que le remboursement ne peut jamais échouer.
Sanjeev Kumar Dangi

9
@SanjeevKumarDangi c'est moins probable et même en cas d'échec, il peut être facilement traité depuis le compte de dépôt et le compte réel sont sous le contrôle de la banque.
Gldraphael

30

Je pense que la sagesse classique est de ne jamais laisser des transactions dépasser les frontières du microservice. Si un ensemble de données donné doit réellement être cohérent avec un autre, ces deux choses vont ensemble.

C'est l'une des raisons pour lesquelles il est très difficile de scinder un système en services avant de l'avoir entièrement conçu. Ce qui dans le monde moderne signifie probablement écrit


37
Une telle approche pourrait très bien conduire à la fusion de tous les microservices en une seule application monolithique.
Slava Fomin II

4
C'est la bonne approche. C'est en fait simple: si vous avez besoin d'une transaction entre services, vos services sont incorrects: remodelez-les! @ SlaveFominII ce que vous dites n'est vrai que si vous ne savez pas comment concevoir un système de microservice. Si vous vous retrouvez à vous opposer à l'approche de microservices, ne le faites pas, votre monolithe sera meilleur qu'un mauvais modèle de microservice. Vous devez diviser le monolithe en services uniquement lorsque vous trouvez les bonnes limites de service. Sinon, l'utilisation de microservices n'est pas le bon choix d'architecture, elle ne fait que suivre le battage publicitaire.
Francesc Castells le

@FrancescCastells Que se passe-t-il si nos services nécessitent réellement une transaction entre services, voulez-vous dire que nous devrions ignorer les contextes liés et modéliser nos services de manière à ce qu'ils aboutissent à une transaction unique? Je suis un débutant dans les microservices, toujours en train de lire, alors excusez ma question si cela semble naïf ....: D: D
Bilbo Baggins

@BilboBaggins Je veux dire le contraire d'ignorer les contextes liés. Par définition, les transactions se produisent dans un contexte limité et non à travers plusieurs d'entre elles. Par conséquent, si vous concevez correctement vos microservices avec vos contextes liés, vos transactions ne doivent pas couvrir plusieurs services. Notez que, parfois, ce dont vous avez besoin n'est pas des transactions, mais une meilleure gestion de la cohérence éventuelle et des actions de compensation appropriées lorsque les choses ne se passent pas correctement.
Francesc Castells

Je ne comprends pas ce que vous voulez dire, y a-t-il une chance que nous puissions en discuter dans un chat?
Bilbo Baggins

17

Je pense que si la cohérence est une exigence forte de votre application, vous devriez vous demander si microservices est la meilleure approche. Comme le dit Martin Fowler :

Les microservices posent d’éventuels problèmes de cohérence en raison de leur insistance louable sur la gestion décentralisée des données. Avec un monolithe, vous pouvez mettre à jour un tas de choses en une seule transaction. Les microservices nécessitent plusieurs ressources pour la mise à jour et les transactions distribuées sont mal vues (pour une bonne raison). À présent, les développeurs doivent être conscients des problèmes de cohérence et savoir comment détecter les éléments désynchronisés avant de faire quoi que ce soit regretté par le code.

Mais peut-être que dans votre cas, vous pouvez sacrifier la cohérence en pos de disponibilité

Les processus métier sont souvent plus tolérants en matière d'incohérences que vous ne le pensez, car les entreprises accordent souvent plus d'importance à la disponibilité.

Cependant, je me demande également s’il existe une stratégie pour les transactions distribuées dans les microservices, mais le coût est peut-être trop élevé. Je voulais vous donner mes deux sous avec l'excellent article de Martin Fowler et le théorème de la PAC .


1
Dans les transactions commerciales distribuées, la cohérence est parfois résolue en ajoutant une couche d'orchestration, telle qu'un moteur de flux de travail ou une file d'attente soigneusement conçue. Cette couche gère toutes les validations et les restaurations en deux phases et permet aux microservices de se concentrer sur une logique métier spécifique. Mais revenons à CAP: investir dans la disponibilité et la cohérence fait de la performance la victime. Comment les microservices se comparent-ils aux nombreuses classes découplées constituant votre entreprise en POO?
Greg

Vous pouvez utiliser RAFT via des microservices pour traiter la cohérence FWIW
f0ster le

1
+1 Je me demande souvent pourquoi, dans un contexte de microservices, des transactions sont souhaitées, si toutes les vues «extraites» des données peuvent être implémentées en tant que vues matérialisées. Par exemple, si un microservice implémente les débits d'un compte et les autres crédits d'un autre compte, les vues des soldes ne prennent en compte que les paires de crédits et les débits, les crédits et les débits non appariés restant dans les tampons de la vue matérialisée.
Sentinel

16

Comme suggéré dans au moins une des réponses ici mais également ailleurs sur le Web, il est possible de concevoir un microservice qui persiste les entités ensemble dans une transaction normale si vous avez besoin de cohérence entre les deux entités.

Mais dans le même temps, il se peut que les entités n'appartiennent pas vraiment au même microservice, par exemple des enregistrements de vente et des enregistrements de commande (lorsque vous commandez quelque chose pour réaliser la vente). Dans ce cas, vous aurez peut-être besoin d’un moyen d’assurer la cohérence entre les deux microservices.

Les transactions distribuées traditionnellement ont été utilisées et, selon mon expérience, elles fonctionnent bien jusqu'à ce qu'elles atteignent une taille où le verrouillage devient un problème. Vous pouvez assouplir le verrouillage de sorte que seules les ressources pertinentes (par exemple, l'élément vendu) soient "verrouillées" à l'aide d'un changement d'état, mais c'est là que le problème commence à se poser car vous entrez sur le territoire où vous devez créer tous les éléments. logique de le faire, vous-même, plutôt que d'avoir, disons, une base de données le gérer pour vous.

J'ai travaillé avec des entreprises qui se sont lancées dans la construction de leur propre cadre de transaction pour traiter ce problème complexe, mais je ne le recommande pas, car il coûte cher et prend du temps à mûrir.

Il existe des produits qui peuvent être intégrés à votre système et qui assurent la cohérence. Un moteur de processus métier est un bon exemple et ils gèrent généralement la cohérence en fin de compte et en utilisant une compensation. D'autres produits fonctionnent de la même manière. Vous vous retrouvez généralement avec une couche de logiciels à proximité du ou des clients, qui traite de la cohérence et des services de transactions et d'appels (micro) pour effectuer le traitement commercial proprement dit . L'un de ces produits est un connecteur JCA générique qui peut être utilisé avec les solutions Java EE (pour la transparence: je suis l'auteur). Voir http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html pour plus de détails et une discussion plus approfondie des problèmes soulevés ici.

Une autre façon de gérer les transactions et la cohérence consiste à intégrer un appel à un microservice dans un appel à quelque chose de transactionnel, tel qu'une file d'attente de messages. Prenez l'exemple de l'enregistrement de vente / enregistrement de commande ci-dessus - vous pouvez simplement laisser le microservice de vente envoyer un message au système de commande, lequel est validé dans la même transaction que celle qui écrit la vente dans la base de données. Le résultat est une solution asynchrone qui évolue très bien. En utilisant des technologies telles que les sockets Web, vous pouvez même contourner le problème de blocage souvent lié à la mise à l'échelle de solutions asynchrones. Pour plus d’idées sur de tels modèles, consultez un autre de mes articles: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

Quelle que soit la solution choisie, il est important de reconnaître que seule une petite partie de votre système écrit des éléments qui doivent être cohérents - la plupart des accès sont susceptibles d'être en lecture seule. Pour cette raison, construisez la gestion des transactions uniquement dans les parties pertinentes du système, de sorte qu'elle puisse toujours évoluer correctement.


Dans l'intervalle, je dirais qu'il faut sérieusement envisager de transformer le processus en processus asynchrone, où chaque étape du processus est entièrement transactionnelle. Voir ici pour plus de détails: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Ant Kutschera

"Créez l'exemple de vente / exemple de commande ci-dessus - vous pouvez simplement laisser le microservice de vente envoyer un message au système de commande, lequel est validé dans la même transaction que celle qui écrit la vente dans la base de données." Vous ne savez pas si ce que vous suggérez est essentiellement une transaction distribuée, cela dit, comment géreriez-vous le scénario de restauration dans ce cas? Par exemple, un message est validé dans la file de messages mais est annulé du côté de la base de données.
Samedi

@sactiw ne sait pas si j'aurais peut-être eu une phase de validation en deux phases, mais je l'éviterais maintenant d'écrire mes données commerciales et le fait qu'un message doit être ajouté à la file d'attente, dans une transaction vers ma base de données microservice. . Le "fait", à savoir une "commande", est ensuite traité de manière asynchrone après la validation de la transaction, à l'aide d'un mécanisme de nouvelle tentative automatisé. Voir l'article du blog du 2018-03-10 pour un exemple. Évitez si possible les retours en arrière ou les compensations en faveur d’une stratégie d’avant, car ils sont plus faciles à mettre en œuvre.
Ant Kutschera

1

Dans les microservices, il existe trois façons d’obtenir une cohérence entre diff. prestations de service:

  1. Orchestration - Un processus qui gère la transaction et la restauration sur plusieurs services.

  2. Chorégraphie - Les services échangent des messages et parviennent à un état cohérent.

  3. Hybride - Mélange des deux ci-dessus.

Pour une lecture complète, visitez le lien: https://medium.com/capital-one-developers/microservices-when-to-react-vs-orchestrate-c6b18308a14c.


1

Il existe de nombreuses solutions qui compromettent plus que je ne suis à l'aise avec. Certes, si votre cas d'utilisation est complexe, tel que le transfert d'argent entre différentes banques, des alternatives plus agréables peuvent s'avérer impossibles. Mais regardons ce que nous pouvons faire dans le scénario courant, où l'utilisation de microservices interfère avec nos transactions de base de données potentielles.

Option 1: éviter les transactions si cela est possible

Évident et mentionné auparavant, mais idéal si nous pouvons le gérer. Les composants appartiennent-ils réellement au même microservice? Ou pouvons-nous repenser le (s) système (s) de telle sorte que la transaction devienne inutile? Peut-être que l'acceptation de la non-transactionalité est le sacrifice le plus abordable.

Option 2: Utiliser une file d'attente

S'il est suffisamment certain que l'autre service réussira dans tous les domaines, nous pouvons l'appeler via une forme de file d'attente. L'élément en file d'attente ne sera récupéré que plus tard, mais nous pouvons nous assurer qu'il est en file d'attente .

Par exemple, supposons que nous voulions insérer une entité et envoyer un courrier électronique, en tant que transaction unique. Au lieu d'appeler le serveur de messagerie, nous mettons le courrier électronique dans une table.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Un inconvénient évident est que plusieurs microservices devront avoir accès à la même table.

Option 3: Effectuez le travail externe en dernier, juste avant de terminer la transaction.

Cette approche repose sur l'hypothèse qu'il est très peu probable que l'engagement de la transaction échoue.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Si les requêtes échouent, l'appel externe n'a pas encore eu lieu. Si l'appel externe échoue, la transaction n'est jamais validée.

Cette approche est limitée par le fait que nous ne pouvons émettre qu’un seul appel externe et que cela doit être fait en dernier (c’est-à-dire que nous ne pouvons pas utiliser le résultat obtenu dans nos requêtes).

Option 4: Créer des objets en attente

Comme indiqué ici , plusieurs microservices peuvent créer différents composants, chacun dans un état en attente, de manière non transactionnelle.

Toute validation est effectuée, mais rien n'est créé dans un état définitif. Une fois que tout a été créé avec succès, chaque composant est activé. Habituellement, cette opération est si simple et les probabilités que quelque chose ne va pas sont si petites que nous pourrions même préférer procéder à l'activation de manière non-transactionnelle.

Le plus gros inconvénient est probablement que nous devons tenir compte de l'existence d'éléments en attente. Toute requête sélectionnée doit déterminer s'il faut inclure les données en attente. La plupart devraient l'ignorer. Et les mises à jour sont une autre histoire.

Option 5: laisser le microservice partager sa requête

Aucune des autres options ne le fait pour vous? Alors soyons peu orthodoxes .

Selon l'entreprise, celle-ci peut être inacceptable. Je suis au courant. Ceci est peu orthodoxe. Si ce n'est pas acceptable, prenez une autre route. Mais si cela correspond à votre situation, le problème sera résolu de manière simple et puissante. Ce pourrait être juste le compromis le plus acceptable.

Il existe un moyen de transformer les requêtes de plusieurs microservices en une simple transaction de base de données.

Renvoie la requête plutôt que de l'exécuter.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

En ce qui concerne le réseau, chaque microservice doit pouvoir accéder à chaque base de données. Gardez cela à l'esprit, y compris en ce qui concerne la mise à l'échelle future.

Si les bases de données impliquées dans la transaction sont sur le même serveur, il s'agira d'une transaction normale. S'ils sont sur des serveurs différents, ce sera une transaction distribuée. Le code est le même quel que soit.

Nous recevons la requête, y compris son type de connexion, ses paramètres et sa chaîne de connexion. Nous pouvons le résumer dans une classe de commandes exécutable soignée, en gardant le flux lisible: L'appel de microservice génère une commande que nous exécutons dans le cadre de notre transaction.

La chaîne de connexion correspond à ce que nous fournit le microservice d'origine. Par conséquent, la requête est toujours considérée comme exécutée par ce microservice. Nous sommes simplement en train de l'acheminer physiquement via le microservice client. Cela fait-il une différence? Eh bien, cela nous permet de le mettre dans la même transaction avec une autre requête.

Si le compromis est acceptable, cette approche nous donne la transactionnalité simple d’une application monolithique, dans une architecture de microservice.


0

Je commencerais par décomposer l’espace posant problème - en identifiant les limites de votre service . Lorsque cela est fait correctement, vous n’auriez jamais besoin de transactions entre services.

Chaque service a ses propres données, comportement, forces de motivation, règles administratives, règles administratives, etc. Un bon point de départ consiste à répertorier les fonctionnalités de haut niveau dont dispose votre entreprise. Par exemple, marketing, vente, comptabilité, support. La structure organisationnelle est un autre point de départ, mais sachez qu'il y a une mise en garde. Pour certaines raisons (politiques, par exemple), il se peut que ce ne soit pas le schéma de décomposition optimal des entreprises. Une approche plus stricte est l' analyse de la chaîne de valeur . N'oubliez pas que vos services peuvent également inclure des personnes, ce n'est pas strictement un logiciel. Les services doivent communiquer les uns avec les autres via des événements .

La prochaine étape consiste à sculpter ces services. En conséquence, vous obtenez des agrégats encore relativement indépendants . Ils représentent une unité de cohérence. En d'autres termes, leurs internes doivent être cohérents et ACID. Les agrégats communiquent les uns avec les autres via des événements.

Si vous pensez que votre domaine exige d'abord la cohérence, détrompez-vous. Aucun des grands systèmes critiques n’est conçu dans cette optique. Ils sont tous distribués et finalement cohérents. Vérifiez le papier classique de Pat Helland .

Voici quelques conseils pratiques sur la création d’un système distribué.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.