Disons que nous avons un utilisateur, des microservices Wallet REST et une passerelle API qui rassemble les choses. Lorsque Bob s'inscrit sur notre site Web, notre passerelle API doit créer un utilisateur via le microservice Utilisateur et un portefeuille via le microservice Wallet.
Voici maintenant quelques scénarios où les choses pourraient mal tourner:
La création de l'utilisateur Bob échoue: c'est OK, nous renvoyons simplement un message d'erreur au Bob. Nous utilisons des transactions SQL, donc personne n'a jamais vu Bob dans le système. Tout est bien :)
L'utilisateur Bob est créé mais avant que notre portefeuille ne puisse être créé, notre passerelle API plante. Nous avons maintenant un utilisateur sans portefeuille (données incohérentes).
L'utilisateur Bob est créé et au fur et à mesure que nous créons le portefeuille, la connexion HTTP est interrompue. La création du portefeuille a peut-être réussi ou non.
Quelles solutions sont disponibles pour éviter ce type d'incohérence des données? Existe-t-il des modèles qui permettent aux transactions de couvrir plusieurs demandes REST? J'ai lu la page Wikipédia sur le commit en deux phases qui semble aborder ce problème, mais je ne sais pas comment l'appliquer dans la pratique. Ce document Atomic Distributed Transactions: un article de conception RESTful semble également intéressant même si je ne l'ai pas encore lu.
Sinon, je sais que REST pourrait ne pas convenir à ce cas d'utilisation. La manière correcte de gérer cette situation serait-elle peut-être d'abandonner complètement REST et d'utiliser un protocole de communication différent comme un système de file d'attente de messages? Ou devrais-je appliquer la cohérence dans le code de mon application (par exemple, en ayant un travail d'arrière-plan qui détecte les incohérences et les corrige ou en ayant un attribut «état» sur mon modèle utilisateur avec des valeurs «création», «créé», etc.)?
la source
Réponses:
Ce qui n'a pas de sens:
Qu'est-ce qui vous donnera des maux de tête:
Quelle est probablement la meilleure alternative:
Mais que faire si vous avez besoin de réponses synchrones?
la source
C'est une question classique qui m'a été posée récemment lors d'une interview Comment appeler plusieurs services Web tout en conservant une sorte de gestion des erreurs au milieu de la tâche. Aujourd'hui, dans le calcul haute performance, on évite les engagements en deux phases. J'ai lu un article il y a de nombreuses années sur ce qu'on appelait le «modèle Starbuck» pour les transactions: Pensez au processus de commande, de paiement, de préparation et de réception du café que vous commandez chez Starbuck ... Je simplifie à l'extrême les choses, mais un modèle de validation en deux phases suggérez que l'ensemble du processus consisterait en une seule transaction d'emballage pour toutes les étapes impliquées jusqu'à ce que vous receviez votre café. Cependant, avec ce modèle, tous les employés attendraient et arrêtaient de travailler jusqu'à ce que vous ayez votre café. Vous voyez l'image?
Au lieu de cela, le «modèle Starbuck» est plus productif en suivant le modèle du «meilleur effort» et en compensant les erreurs dans le processus. Tout d'abord, ils s'assurent que vous payez! Ensuite, il y a des files d'attente de messages avec votre commande attachée à la tasse. Si quelque chose ne va pas dans le processus, comme si vous n'avez pas obtenu votre café, ce n'est pas ce que vous avez commandé, etc., nous entrons dans le processus de compensation et nous nous assurons que vous obtenez ce que vous voulez ou vous rembourser, c'est le modèle le plus efficace pour une productivité accrue.
Parfois, Starbuck gaspille un café, mais le processus global est efficace. Il y a d'autres astuces à penser lorsque vous créez vos services Web, comme les concevoir de manière à ce qu'ils puissent être appelés un nombre illimité de fois tout en fournissant le même résultat final. Donc, ma recommandation est:
Ne soyez pas trop fin dans la définition de vos services web (je ne suis pas convaincu du battage médiatique des micro-services ces jours-ci: trop de risques d'aller trop loin);
Async augmente les performances, alors préférez être asynchrone, envoyez des notifications par e-mail chaque fois que possible.
Construisez des services plus intelligents pour les rendre "rappelables" un nombre illimité de fois, en les traitant avec un uid ou un taskid qui suivra l'ordre de bas en haut jusqu'à la fin, en validant les règles métier à chaque étape;
Utilisez des files d'attente de messages (JMS ou autres) et détournez-les vers des processeurs de gestion des erreurs qui appliqueront des opérations à la «restauration» en appliquant des opérations opposées, d'ailleurs, travailler avec un ordre asynchrone nécessitera une sorte de file d'attente pour valider l'état actuel du processus, alors considérez cela;
En dernier recours, (car cela peut ne pas arriver souvent), mettez-le dans une file d'attente pour le traitement manuel des erreurs.
Revenons au problème initial qui a été signalé. Créez un compte et créez un portefeuille et assurez-vous que tout a été fait.
Disons qu'un service Web est appelé pour orchestrer l'ensemble de l'opération.
Le pseudo code du service Web ressemblerait à ceci:
Appelez le microservice de création de compte, transmettez-lui des informations et un microservice de création de compte ID de tâche unique 1.1 vérifiera d'abord si ce compte a déjà été créé. Un identifiant de tâche est associé à l'enregistrement du compte. Le microservice détecte que le compte n'existe pas, il le crée et stocke l'ID de la tâche. REMARQUE: ce service peut être appelé 2000 fois, il effectuera toujours le même résultat. Le service répond par un "reçu contenant des informations minimales pour effectuer une opération d'annulation si nécessaire".
Appelez la création du portefeuille, en lui donnant l'ID de compte et l'ID de tâche. Disons qu'une condition n'est pas valide et que la création du portefeuille ne peut pas être effectuée. L'appel retourne avec une erreur mais rien n'a été créé.
L'orchestrateur est informé de l'erreur. Il sait qu'il doit abandonner la création du compte, mais il ne le fera pas lui-même. Il demandera au service de portefeuille de le faire en transmettant son «reçu d'annulation minimal» reçu à la fin de l'étape 1.
Le service de compte lit le reçu d'annulation et sait comment annuler l'opération; le reçu d'annulation peut même inclure des informations sur un autre microservice qu'il aurait pu appeler lui-même pour faire une partie du travail. Dans cette situation, le reçu d'annulation peut contenir l'ID de compte et éventuellement des informations supplémentaires nécessaires pour effectuer l'opération inverse. Dans notre cas, pour simplifier les choses, disons que c'est simplement supprimer le compte en utilisant son identifiant de compte.
Maintenant, disons que le service Web n'a jamais reçu le succès ou l'échec (dans ce cas) que l'annulation de la création du compte a été effectuée. Il appellera simplement à nouveau le service d'annulation du compte. Et ce service ne devrait normalement jamais échouer car son objectif est que le compte n'existe plus. Il vérifie donc s'il existe et voit que rien ne peut être fait pour l'annuler. Il revient donc que l'opération est un succès.
Le service Web renvoie à l'utilisateur que le compte n'a pas pu être créé.
Ceci est un exemple synchrone. Nous aurions pu le gérer d'une manière différente et placer le cas dans une file d'attente de messages destinée au service d'assistance si nous ne voulions pas que le système récupère complètement l'erreur ". J'ai vu cela se faire dans une entreprise où ce n'est pas assez des hooks pouvaient être fournis au système dorsal pour corriger les situations. Le service d'assistance recevait des messages contenant ce qui avait été effectué avec succès et disposait de suffisamment d'informations pour corriger les choses, tout comme notre reçu d'annulation pouvait être utilisé de manière entièrement automatisée.
J'ai effectué une recherche et le site Web de Microsoft a une description de modèle pour cette approche. C'est ce qu'on appelle le modèle de transaction de compensation:
Modèle de transaction compensatoire
la source
Tous les systèmes distribués ont des problèmes de cohérence transactionnelle. La meilleure façon de faire est, comme vous l'avez dit, d'avoir un commit en deux phases. Faites en sorte que le portefeuille et l'utilisateur soient créés dans un état en attente. Après sa création, effectuez un appel séparé pour activer l'utilisateur.
Ce dernier appel doit être répétable en toute sécurité (au cas où votre connexion serait interrompue).
Cela nécessitera que le dernier appel connaisse les deux tables (afin que cela puisse être fait en une seule transaction JDBC).
Sinon, vous voudrez peut-être réfléchir aux raisons pour lesquelles vous êtes si inquiet pour un utilisateur sans portefeuille. Pensez-vous que cela posera un problème? Si c'est le cas, peut-être que les avoir comme appels de repos séparés est une mauvaise idée. Si un utilisateur ne devrait pas exister sans portefeuille, vous devriez probablement ajouter le portefeuille à l'utilisateur (dans l'appel POST d'origine pour créer l'utilisateur).
la source
À mon humble avis, l'un des aspects clés de l'architecture des microservices est que la transaction est limitée au microservice individuel (principe de responsabilité unique).
Dans l'exemple actuel, la création de l'utilisateur serait une propre transaction. La création d'un utilisateur pousserait un événement USER_CREATED dans une file d'attente d'événements. Le service Wallet s'abonnerait à l'événement USER_CREATED et effectuerait la création du portefeuille.
la source
Si mon portefeuille n'était qu'un autre groupe d'enregistrements dans la même base de données SQL que l'utilisateur, je placerais probablement le code de création de l'utilisateur et du portefeuille dans le même service et le gérerais en utilisant les fonctions de transaction normales de la base de données.
Il me semble que vous vous demandez ce qui se passe lorsque le code de création de portefeuille vous oblige à toucher un autre ou plusieurs autres systèmes? Je dirais que tout dépend de la complexité et / ou du risque du processus de création.
S'il s'agit simplement de toucher un autre magasin de données fiable (par exemple, celui qui ne peut pas participer à vos transactions SQL), alors en fonction des paramètres globaux du système, je serais peut-être prêt à risquer la très petite chance que la deuxième écriture ne se produise pas. Je pourrais ne rien faire, mais soulever une exception et traiter les données incohérentes via une transaction de compensation ou même une méthode ad hoc. Comme je le dis toujours à mes développeurs: "si ce genre de choses se passe dans l'application, cela ne passera pas inaperçu".
À mesure que la complexité et le risque de création de portefeuille augmentent, vous devez prendre des mesures pour atténuer les risques encourus. Supposons que certaines étapes nécessitent l'appel de plusieurs API partenaires.
À ce stade, vous pouvez introduire une file d'attente de messages avec la notion d'utilisateurs et / ou de portefeuilles partiellement construits.
Une stratégie simple et efficace pour vous assurer que vos entités seront finalement correctement construites consiste à réessayer les tâches jusqu'à ce qu'elles réussissent, mais cela dépend en grande partie des cas d'utilisation de votre application.
Je réfléchirais également longuement et sérieusement à la raison pour laquelle j'ai eu une étape sujette à l'échec dans mon processus d'approvisionnement.
la source
Une solution simple consiste à créer un utilisateur à l'aide du service utilisateur et à utiliser un bus de messagerie où le service utilisateur émet ses événements, et le service portefeuille s'inscrit sur le bus de messagerie, écoute l'événement créé par l'utilisateur et crée un portefeuille pour l'utilisateur. En attendant, si l'utilisateur accède à l'interface utilisateur de Wallet pour voir son portefeuille, vérifiez si l'utilisateur vient d'être créé et montrez que la création de votre portefeuille est en cours, veuillez vérifier un certain temps.
la source
Traditionnellement, des gestionnaires de transactions distribués sont utilisés. Il y a quelques années, dans le monde Java EE, vous avez peut-être créé ces services en tant que qu'EJB qui ont été déployés sur différents nœuds et votre passerelle API aurait effectué des appels à distance vers ces EJB. Le serveur d'applications (s'il est correctement configuré) garantit automatiquement, à l'aide de la validation en deux phases, que la transaction est soit validée, soit annulée sur chaque nœud, de sorte que la cohérence est garantie. Mais cela nécessite que tous les services soient déployés sur le même type de serveur d'application (afin qu'ils soient compatibles) et en réalité ne fonctionnent qu'avec des services déployés par une seule entreprise.
Pour SOAP (ok, pas REST), il y a la spécification WS-AT mais aucun service que j'ai jamais eu à intégrer ne prend en charge cela. Pour REST, JBoss a quelque chose dans le pipeline . Sinon, le «modèle» est soit de trouver un produit que vous pouvez brancher dans votre architecture, soit de construire votre propre solution (non recommandé).
J'ai publié un tel produit pour Java EE: https://github.com/maxant/genericconnector
Selon l'article auquel vous faites référence, il existe également le modèle Try-Cancel / Confirm et le produit associé d'Atomikos.
Les moteurs BPEL gèrent la cohérence entre les services déployés à distance à l'aide de la compensation.
Il existe de nombreuses façons de «lier» des ressources non transactionnelles dans une transaction:
Prôner le jeu des démons: pourquoi construire quelque chose comme ça, quand il y a des produits qui font ça pour vous (voir ci-dessus), et le font probablement mieux que vous ne le pouvez, car ils sont essayés et testés?
la source
Personnellement j'aime l'idée des Micro Services, des modules définis par les cas d'utilisation, mais comme votre question le mentionne, ils ont des problèmes d'adaptation pour les entreprises classiques comme les banques, l'assurance, les télécoms, etc ...
Les transactions distribuées, comme beaucoup l'ont mentionné, ne sont pas un bon choix, les gens optent désormais davantage pour des systèmes finalement cohérents, mais je ne suis pas sûr que cela fonctionnera pour les banques, les assurances, etc.
J'ai écrit un blog sur ma solution proposée, peut-être que cela peut vous aider ...
https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/
la source
La cohérence éventuelle est la clé ici.
Le commandant est en charge de la transaction distribuée et prend le contrôle. Il connaît l'instruction à exécuter et coordonnera leur exécution. Dans la plupart des scénarios, il n'y aura que deux instructions, mais il peut gérer plusieurs instructions.
Le commandant assume la responsabilité de garantir l'exécution de toutes les instructions, ce qui signifie qu'il se retire. Lorsque le commandant tente d'effectuer la mise à jour à distance et n'obtient pas de réponse, il n'a pas de nouvelle tentative. De cette façon, le système peut être configuré pour être moins sujet aux pannes et il se guérit tout seul.
Comme nous avons des tentatives, nous avons l'idempotence. L'idempotence est la propriété de pouvoir faire quelque chose deux fois de manière à ce que les résultats finaux soient les mêmes que si cela avait été fait une seule fois. Nous avons besoin d'une idempotence au niveau du service distant ou de la source de données afin que, dans le cas où il reçoit l'instruction plus d'une fois, il ne la traite qu'une seule fois.
Cohérence à terme Cela résout la plupart des problèmes de transaction distribuée, mais nous devons considérer quelques points ici. Chaque transaction échouée sera suivie d'une nouvelle tentative, le nombre de tentatives dépend du contexte.
La cohérence est éventuelle, c'est-à-dire pendant que le système n'est pas dans un état cohérent lors d'une nouvelle tentative, par exemple si un client a commandé un livre et effectué un paiement, puis met à jour la quantité en stock. Si les opérations de mise à jour du stock échouent et en supposant qu'il s'agissait du dernier stock disponible, le livre sera toujours disponible jusqu'à ce que l'opération de nouvelle tentative de mise à jour du stock ait réussi. Une fois la nouvelle tentative réussie, votre système sera cohérent.
la source
Pourquoi ne pas utiliser la plate-forme de gestion des API (APIM) qui prend en charge les scripts / la programmation? Ainsi, vous pourrez créer un service composite dans l'APIM sans déranger les micro-services. J'ai conçu en utilisant APIGEE à cet effet.
la source