web-dev-qa-db-fra.com

Cohérence base de données avec microservices

Quel est le meilleur moyen de parvenir à la cohérence des bases de données dans les systèmes basés sur microservices?

Au GOTO in Berlin , Martin Fowler parlait de microservices et l'une des "règles" qu'il mentionnait était de conserver les bases de données "par service", ce qui signifie que les services ne peuvent pas se connecter directement à une base de données "détenue" par un autre. un service.

C'est super beau et élégant, mais dans la pratique, cela devient un peu délicat. Supposons que vous ayez quelques services:

  • une interface
  • un service de gestion des commandes
  • un programme de fidélisation

Maintenant, un client effectue un achat sur votre interface, qui appellera le service de gestion des commandes, ce qui enregistrera tout dans la base de données - aucun problème. À ce stade, il sera également fait appel au service de programme de fidélité pour qu'il crédite/débite des points de votre compte.

Désormais, lorsque tout est sur le même serveur de base de données/base de données, tout devient facile, car vous pouvez tout exécuter en une seule transaction: si le service de programme de fidélité n'écrit pas dans la base de données, nous pouvons tout restaurer.

Lorsque nous effectuons des opérations de base de données sur plusieurs services, cela n’est pas possible car nous ne comptons pas sur une seule connexion ni ne tirons parti de l’exécution d’une seule transaction. Quels sont les meilleurs modèles pour que les choses restent cohérentes et vivent heureux? la vie?

J'ai hâte d'entendre vos suggestions! ... et merci d'avance!

29
odino

C'est super beau et élégant mais en pratique ça devient un peu délicat

En pratique, cela signifie que vous devez concevoir vos microservices de manière à assurer la cohérence métier nécessaire lorsque vous respectez la règle:

ces services ne peuvent pas se connecter directement à une base de données "détenue" par un autre service.

En d’autres termes, ne formulez aucune hypothèse sur leurs responsabilités et modifiez les limites au besoin jusqu’à ce que vous trouviez le moyen de faire en sorte que cela fonctionne.

Maintenant, à votre question:

Quels sont les meilleurs modèles pour garder les choses cohérentes et vivre une vie heureuse?

Pour les choses qui n'exigent pas une cohérence immédiate, et la mise à jour des points de fidélité semble appartenir à cette catégorie, vous pouvez utiliser un modèle pub/sous fiable pour répartir les événements d'un microservice à traiter par d'autres. Le bit le plus fiable est que vous souhaitiez de bonnes tentatives, une annulation et une idempotence (ou une transactionnalité) pour le traitement des événements.

Si vous utilisez .NET, voici quelques exemples d’infrastructures prenant en charge ce type de fiabilité: NServiceBus et MassTransit . Divulgation complète - Je suis le fondateur de NServiceBus.

Mise à jour: Suite aux commentaires concernant les points de fidélité: "si les mises à jour du solde sont traitées avec un retard, un client peut en réalité commander plus d’articles que de points". 

Beaucoup de gens ont du mal à respecter ce type d’exigence de cohérence. Le fait est que ce type de scénario peut généralement être traité en introduisant des règles supplémentaires, par exemple si un utilisateur se voit notifier des points de fidélité négatifs. Si T passe sans que les points de fidélité soient résolus, informez-en que des frais seront facturés à M en fonction d'un taux de conversion. Cette politique doit être visible pour les clients lorsqu'ils utilisent des points pour acheter des objets.

11
Udi Dahan

Je ne traite généralement pas de microservices, et cela n’est peut-être pas une bonne façon de faire les choses, mais voici une idée:

Pour reformuler le problème, le système est constitué de trois parties indépendantes mais communicantes: le client, le serveur de gestion des commandes et le programme de fidélité. Le client veut s'assurer qu'un état est sauvegardé à la fois dans le backend de gestion des commandes et dans le backend du programme de fidélité.

Une solution possible serait d'implémenter un type de commit en deux phases :

  1. Premièrement, le frontend place un enregistrement dans sa propre base de données avec toutes les données. Appelez ceci le record frontend .
  2. L'interface client demande au système de gestion des commandes un ID de transaction et lui transmet toutes les données nécessaires à l'exécution de l'action. Le backend de gestion des commandes stocke ces données dans une zone de stockage intermédiaire, y associant un nouvel ID de transaction et le renvoyant au client.
  3. L'ID de transaction de gestion des commandes est stocké dans le cadre de l'enregistrement frontal.
  4. L'interface demande au programme de fidélisation un ID de transaction et lui transmet toutes les données nécessaires à l'exécution de l'action. Le backend du programme de fidélité stocke ces données dans une zone de stockage intermédiaire, en y associant un nouvel ID de transaction et en le renvoyant au client.
  5. L'ID de transaction du programme de fidélité est stocké dans l'enregistrement frontal.
  6. L'interface indique au gestionnaire de gestion des commandes de finaliser la transaction associée à l'identifiant de transaction stocké.
  7. Le client demande au programme de fidélisation de finaliser la transaction associée à l’ID de transaction stocké.
  8. Le frontend supprime son enregistrement frontend.

Si cela est mis en œuvre, les modifications ne seront pas nécessairement atomic , mais elles seront éventuellement cohérentes . Pensons aux endroits où il pourrait échouer:

  • Si cela échoue à la première étape, aucune donnée ne changera.
  • Si le système échoue aux deuxième, troisième, quatrième ou cinquième virements, lorsque le système revient en ligne, il peut parcourir tous les enregistrements frontaux, en recherchant des enregistrements sans ID de transaction associé (de l'un ou l'autre type). S'il rencontre un tel enregistrement, il peut être rejoué à partir de l'étape 2. (En cas d'échec aux étapes 3 ou 5, il restera des enregistrements abandonnés dans le backend, mais ils ne seront jamais sortis de la zone intermédiaire. c'est bon.)
  • S'il échoue à la sixième, septième ou huitième étape, lorsque le système revient en ligne, il peut rechercher tous les enregistrements frontaux avec les deux ID de transaction renseignés. Il peut ensuite interroger le backend pour connaître l'état de ces transactions, qu'elles soient validées ou non. . Selon ce qui a été commis, cela peut reprendre à partir de l'étape appropriée.
7
icktoofay

Même pour les transactions distribuées, vous pouvez entrer dans «statut en cas de doute» si l'un des participants se bloque au milieu de la transaction. Si vous concevez les services comme une opération idempotente, la vie devient un peu plus facile. On peut écrire des programmes pour remplir les conditions commerciales sans XA. Pat Helland a écrit un excellent article sur ce sujet intitulé "Life Beyond XA". En gros, l’approche consiste à formuler des hypothèses aussi minimes que possible sur les entités distantes. Il a également illustré une approche appelée Open Nested Transactions ( http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf ) pour modéliser les processus métier. Dans ce cas particulier, la transaction d'achat serait un flux de premier niveau et la gestion de la fidélité et des commandes serait des flux de prochain niveau. L'astuce consiste à créer des services granulaires en tant que services idempotents avec une logique de compensation. Ainsi, si quelque chose échoue n'importe où dans le flux, des services individuels peuvent le compenser. Donc, par exemple Si la commande échoue pour une raison quelconque, la fidélité peut déduire le point accumulé pour cet achat.

Une autre approche consiste à modéliser en utilisant une cohérence éventuelle en utilisant une méthode CALM ou CRDT. J'ai écrit un blog pour mettre en évidence l'utilisation de CALM dans la vie réelle - http://shripad-agashe.github.io/2015/08/Art-Of-Disorder-Programming Peut-être que cela vous aidera.

0
Shripad

Je suis d'accord avec ce que @Udi Dahan a dit. Je veux juste ajouter à sa réponse. 

Je pense que vous devez maintenir la demande auprès du programme de fidélisation afin qu’elle échoue à un autre moment. Il y a différentes façons de Word/faire ceci.

1) Rendez l’échec de l’API du programme de fidélité récupérable. C'est-à-dire qu'il peut persister dans les requêtes afin qu'elles ne soient pas perdues et puissent être récupérées (réexécutées) ultérieurement. 

2) Exécutez les demandes du programme de fidélité de manière asynchrone. C'est-à-dire, persistez la demande quelque part d'abord, puis autorisez le service à la lire à partir de ce magasin persistant. Ne supprimez du magasin persistant que si l'exécution est réussie. 

3) Faites ce que Udi a dit et placez-le dans une bonne file d'attente (modèle de publication/sous pour être exact). Cela nécessite généralement que l’abonné fasse l’une des deux choses suivantes: persistez la demande avant de la retirer de la file (goto 1) --OU-- commencez par emprunter la demande de la file d’attente, puis, après avoir traité la demande, avec succès retiré de la file d'attente (c'est ma préférence). 

Tous trois accomplissent la même chose. Ils déplacent la demande vers un endroit conservé où elle peut être traitée jusqu'à son achèvement. La demande n'est jamais perdue et réessayée si nécessaire jusqu'à atteindre un état satisfaisant.

J'aime utiliser l'exemple d'une course de relais. Chaque service ou morceau de code doit s'emparer de la demande avant de permettre à l'ancien morceau de code de le lâcher. Une fois le transfert effectué, le propriétaire actuel ne doit pas perdre la demande tant qu'elle n'a pas été traitée ou transférée à un autre élément de code.

0
Jose Martinez