BLOG | NGINX

Gestion des données pilotée par les événements pour les microservices

NGINX-Partie-de-F5-horiz-black-type-RGB
Miniature de Chris Richardson
Chris Richardson
Publié le 4 décembre 2015

Rédacteur – Cette série d’articles en sept parties est désormais terminée :

  1. Introduction aux microservices
  2. Création de microservices : Utilisation d'une passerelle API
  3. Création de microservices : Communication inter-processus dans une architecture de microservices
  4. Découverte de services dans une architecture de microservices
  5. Gestion des données pilotée par événements pour les microservices (cet article)
  6. Choisir une stratégie de déploiement de microservices
  7. Refactorisation d'un monolithe en microservices

Vous pouvez également télécharger l'ensemble complet des articles, ainsi que des informations sur la mise en œuvre de microservices à l'aide de NGINX Plus, sous forme d'ebook – Microservices : De la conception au déploiement . Et consultez notre série sur l’ architecture de référence des microservices et la page Solutions de microservices .

Il s'agit du cinquième article d'une série sur la création d'applications avec des microservices. Le premier article présente le modèle d’architecture des microservices et discute des avantages et des inconvénients de l’utilisation des microservices. Les deuxième et troisième articles de la série décrivent différents aspects de la communication au sein d’une architecture de microservices. Le quatrième article explore le problème étroitement lié de la découverte de services. Dans cet article, nous changeons de vitesse et examinons les problèmes de gestion des données distribuées qui surviennent dans une architecture de microservices.

Les microservices et le problème de la gestion des données distribuées

Une application monolithique possède généralement une seule base de données relationnelle. L’un des principaux avantages de l’utilisation d’une base de données relationnelle est que votre application peut utiliser des transactions ACID , qui offrent des garanties importantes :

  • Atomicité – Les modifications sont effectuées de manière atomique
  • Cohérence – L’état de la base de données est toujours cohérent
  • Isolation – Même si les transactions sont exécutées simultanément, il semble qu'elles soient exécutées en série
  • Durabilité – Une fois qu'une transaction est validée, elle ne peut pas être annulée

Par conséquent, votre application peut simplement démarrer une transaction, modifier (insérer, mettre à jour et supprimer) plusieurs lignes et valider la transaction.

Un autre grand avantage de l’utilisation d’une base de données relationnelle est qu’elle fournit SQL, qui est un langage de requête riche, déclaratif et standardisé. Vous pouvez facilement écrire une requête qui combine les données de plusieurs tables. Le planificateur de requêtes RDBMS détermine ensuite la manière la plus optimale d’exécuter la requête. Vous n’avez pas à vous soucier des détails de bas niveau tels que la manière d’accéder à la base de données. Et comme toutes les données de votre application se trouvent dans une seule base de données, elles sont faciles à interroger.

Malheureusement, l’accès aux données devient beaucoup plus complexe lorsque nous passons à une architecture de microservices. C'est parce que les données appartenant à chaque microservice sont privées pour ce microservice et ne sont accessibles que via son API. L'encapsulation des données garantit que les microservices sont faiblement couplés et peuvent évoluer indépendamment les uns des autres. Si plusieurs services accèdent aux mêmes données, les mises à jour de schéma nécessitent des mises à jour coordonnées et chronophages pour tous les services.

Pour couronner le tout, différents microservices utilisent souvent différents types de bases de données. Les applications modernes stockent et traitent divers types de données et une base de données relationnelle n'est pas toujours le meilleur choix. Pour certains cas d’utilisation, une base de données NoSQL particulière peut avoir un modèle de données plus pratique et offrir des performances et une évolutivité bien meilleures. Par exemple, il est logique qu'un service qui stocke et interroge du texte utilise un moteur de recherche de texte tel qu'Elasticsearch. De même, un service qui stocke des données de graphiques sociaux devrait probablement utiliser une base de données de graphiques, telle que Neo4j. Par conséquent, les applications basées sur des microservices utilisent souvent un mélange de bases de données SQL et NoSQL, ce que l’on appelle l’approche de persistance polyglotte .

Une architecture partitionnée et polyglotte persistante pour le stockage de données présente de nombreux avantages, notamment des services faiblement couplés et de meilleures performances et évolutivité. Cependant, cela introduit certains défis en matière de gestion des données distribuées.

Le premier défi consiste à mettre en œuvre des transactions commerciales qui maintiennent la cohérence entre plusieurs services. Pour comprendre pourquoi cela constitue un problème, examinons l’exemple d’une boutique B2B en ligne. Le service clientèle conserve des informations sur les clients, y compris leurs lignes de crédit. Le Service Commande gère les commandes et doit vérifier qu'une nouvelle commande ne dépasse pas la limite de crédit du client. Dans la version monolithique de cette application, le service de commande peut simplement utiliser une transaction ACID pour vérifier le crédit disponible et créer la commande.

En revanche, dans une architecture de microservices, les tables ORDER et CUSTOMER sont privées pour leurs services respectifs, comme illustré dans le diagramme suivant.

Chaque service d'une architecture de microservices gère une table de base de données privée

Le service de commande ne peut pas accéder directement à la table CUSTOMER. Il ne peut utiliser que l'API fournie par le service client. Le service de commande pourrait potentiellement utiliser des transactions distribuées , également appelées validation en deux phases (2PC). Cependant, le 2PC n’est généralement pas une option viable dans les applications modernes. Le théorème CAP vous oblige à choisir entre la disponibilité et la cohérence de style ACID, et la disponibilité est généralement le meilleur choix. De plus, de nombreuses technologies modernes, comme la plupart des bases de données NoSQL, ne prennent pas en charge le 2PC. Le maintien de la cohérence des données entre les services et les bases de données est essentiel, nous avons donc besoin d'une autre solution.

Le deuxième défi est de savoir comment mettre en œuvre des requêtes qui récupèrent des données à partir de plusieurs services. Par exemple, imaginons que l’application ait besoin d’afficher un client et ses commandes récentes. Si le service de commande fournit une API pour récupérer les commandes d’un client, vous pouvez récupérer ces données à l’aide d’une jointure côté application. L'application récupère le client auprès du Service Client et les commandes du client auprès du Service Commandes. Supposons cependant que le service de commande ne prenne en charge que la recherche de commandes par leur clé primaire (il utilise peut-être une base de données NoSQL qui ne prend en charge que les récupérations basées sur la clé primaire). Dans cette situation, il n’existe aucun moyen évident de récupérer les données nécessaires.

Architecture pilotée par événements

Pour de nombreuses applications, la solution consiste à utiliser une architecture pilotée par événements . Dans cette architecture, un microservice publie un événement lorsqu'un événement notable se produit, par exemple lorsqu'il met à jour une entité commerciale. D’autres microservices s’abonnent à ces événements. Lorsqu'un microservice reçoit un événement, il peut mettre à jour ses propres entités commerciales, ce qui peut conduire à la publication de davantage d'événements.

Vous pouvez utiliser des événements pour implémenter des transactions commerciales qui couvrent plusieurs services. Une transaction se compose d’une série d’étapes. Chaque étape consiste en un microservice mettant à jour une entité commerciale et publiant un événement qui déclenche l'étape suivante. La séquence de diagrammes suivante montre comment vous pouvez utiliser une approche pilotée par les événements pour vérifier le crédit disponible lors de la création d'une commande. Les microservices échangent des événements via un courtier de messages.

  1. Le service de commande crée une commande avec le statut NOUVEAU et publie un événement Commande créée.

    À l'étape 1 d'une vérification de crédit dans une architecture de microservices, le service de commande publie un événement « Commande créée »

  2. Le service client consomme l'événement Commande créée, réserve du crédit pour la commande et publie un événement Crédit réservé.

    Dans une architecture de microservices, la deuxième étape d'une vérification de crédit consiste pour le service client à générer un événement « Crédit réservé »

  3. Le service de commande consomme l'événement Crédit réservé et modifie le statut de la commande en OUVERT.

    Dans une architecture de microservices, la troisième étape d'une vérification de crédit consiste pour le service de commande à définir le statut de la commande sur « Ouvert »

Un scénario plus complexe pourrait impliquer des étapes supplémentaires, telles que la réservation de stocks en même temps que la vérification du crédit du client.

À condition que (a) chaque service mette à jour de manière atomique la base de données et publie un événement (nous y reviendrons plus tard) et que (b) le Message Broker garantisse que les événements sont livrés au moins une fois, vous pouvez alors mettre en œuvre des transactions commerciales qui s'étendent sur plusieurs services. Il est important de noter qu’il ne s’agit pas de transactions ACID. Elles offrent des garanties beaucoup plus faibles, comme celle de la cohérence éventuelle . Ce modèle de transaction a été appelé modèle BASE .

Vous pouvez également utiliser des événements pour gérer des vues matérialisées qui préjoignent les données appartenant à plusieurs microservices. Le service qui gère la vue s'abonne aux événements pertinents et met à jour la vue. Par exemple, le service de mise à jour de la vue des commandes client qui gère une vue des commandes client s'abonne aux événements publiés par le service client et le service de commande.

Dans une architecture de microservices, un service peut s'abonner aux notifications d'événements publiées par d'autres services en tant que déclencheurs d'action

Lorsque le service de mise à jour de la vue des commandes client reçoit un événement client ou de commande, il met à jour la banque de données de la vue des commandes client. Vous pouvez implémenter la vue de commande client à l'aide d'une base de données de documents telle que MongoDB et stocker un document pour chaque client. Le service de requête de vue de commande client gère les demandes d'un client et les commandes récentes en interrogeant la banque de données de vue de commande client.

Une architecture pilotée par événements présente plusieurs avantages et inconvénients. Il permet la mise en œuvre de transactions qui couvrent plusieurs services et assurent une cohérence éventuelle. Un autre avantage est qu’il permet également à une application de conserver des vues matérialisées. L’un des inconvénients est que le modèle de programmation est plus complexe que lors de l’utilisation de transactions ACID. Souvent, vous devez mettre en œuvre des transactions compensatoires pour récupérer après des échecs au niveau de l'application ; par exemple, vous devez annuler une commande si la vérification de crédit échoue. De plus, les applications doivent gérer des données incohérentes. C’est parce que les modifications apportées par les transactions en vol sont visibles. L'application peut également voir des incohérences si elle lit une vue matérialisée qui n'est pas encore mise à jour. Un autre inconvénient est que les abonnés doivent détecter et ignorer les événements en double.

Atteindre l'atomicité

Dans une architecture pilotée par événements, se pose également le problème de la mise à jour atomique de la base de données et de la publication d’un événement. Par exemple, le service de commande doit insérer une ligne dans la table ORDER et publier un événement de création de commande. Il est essentiel que ces deux opérations soient réalisées de manière atomique. Si le service plante après la mise à jour de la base de données mais avant la publication de l'événement, le système devient incohérent. La méthode standard pour garantir l’atomicité consiste à utiliser une transaction distribuée impliquant la base de données et le Message Broker. Cependant, pour les raisons décrites ci-dessus, comme le théorème CAP, c’est exactement ce que nous ne voulons pas faire.

Publication d'événements à l'aide de transactions locales

Une façon d’atteindre l’atomicité est que l’application publie des événements à l’aide d’un processus en plusieurs étapes impliquant uniquement des transactions locales . L’astuce consiste à avoir une table EVENT, qui fonctionne comme une file d’attente de messages, dans la base de données qui stocke l’état des entités commerciales. L'application démarre une transaction de base de données (locale), met à jour l'état des entités métier, insère un événement dans la table EVENT et valide la transaction. Un thread ou un processus d'application distinct interroge la table EVENT, publie les événements sur le courtier de messages, puis utilise une transaction locale pour marquer les événements comme publiés. Le diagramme suivant montre la conception.

Dans une architecture de microservices, obtenez l'atomicité en utilisant uniquement des transactions locales pour publier des événements

Le service de commande insère une ligne dans la table ORDER et insère un événement Order Created dans la table EVENT. Le thread ou le processus Event Publisher interroge la table EVENT pour les événements non publiés, publie les événements, puis met à jour la table EVENT pour marquer les événements comme publiés.

Cette approche présente plusieurs avantages et inconvénients. L'un des avantages est qu'elle garantit la publication d'un événement pour chaque mise à jour sans avoir recours à 2PC. De plus, l'application publie des événements au niveau de l'entreprise, ce qui élimine le besoin de les déduire. L’un des inconvénients de cette approche est qu’elle est potentiellement sujette aux erreurs, car le développeur doit penser à publier les événements. L’une des limites de cette approche est qu’elle est difficile à mettre en œuvre lors de l’utilisation de certaines bases de données NoSQL en raison de leurs capacités limitées de transaction et de requête.

Cette approche élimine le besoin de 2PC en permettant à l’application d’utiliser des transactions locales pour mettre à jour l’état et publier des événements. Voyons maintenant une approche qui permet d’atteindre l’atomicité en demandant à l’application de simplement mettre à jour l’état.

Exploitation d'un journal de transactions de base de données

Une autre façon d’atteindre l’atomicité sans 2PC est que les événements soient publiés par un thread ou un processus qui exploite le journal des transactions ou des validations de la base de données. L’application met à jour la base de données, ce qui entraîne l’enregistrement des modifications dans le journal des transactions de la base de données. Le thread ou le processus Transaction Log Miner lit le journal des transactions et publie les événements sur Message Broker. Le diagramme suivant montre la conception.

Dans une architecture de microservices, obtenez l'atomicité en exploitant le journal des transactions pour les événements

Un exemple de cette approche est le projet open source LinkedIn Databus . Databus exploite le journal des transactions Oracle et publie les événements correspondant aux modifications. LinkedIn utilise Databus pour maintenir la cohérence de divers magasins de données dérivées avec le système d'enregistrement.

Un autre exemple est le mécanisme de flux dans AWS DynamoDB , qui est une base de données NoSQL gérée. Un flux DynamoDB contient la séquence chronologique des modifications (opérations de création, de mise à jour et de suppression) apportées aux éléments d'une table DynamoDB au cours des dernières 24 heures. Une application peut lire ces modifications à partir du flux et, par exemple, les publier sous forme d’événements.

L’exploration des journaux de transactions présente divers avantages et inconvénients. L’un des avantages est qu’il garantit qu’un événement est publié pour chaque mise à jour sans utiliser 2PC. L’exploration des journaux de transactions peut également simplifier l’application en séparant la publication d’événements de la logique métier de l’application. Un inconvénient majeur est que le format du journal des transactions est propre à chaque base de données et peut même changer entre les versions de base de données. En outre, il peut être difficile de procéder à une rétro-ingénierie des événements métier de haut niveau à partir des mises à jour de bas niveau enregistrées dans le journal des transactions.

L'exploration des journaux de transactions élimine le besoin de 2PC en laissant l'application faire une seule chose : mettre à jour la base de données. Voyons maintenant une approche différente qui élimine les mises à jour et s’appuie uniquement sur les événements.

Utilisation de l'approvisionnement d'événements

L'approvisionnement d'événements permet d'atteindre l'atomicité sans 2PC en utilisant une approche radicalement différente, centrée sur les événements, pour la persistance des entités commerciales. Plutôt que de stocker l’état actuel d’une entité, l’application stocke une séquence d’événements de changement d’état. L'application reconstruit l'état actuel d'une entité en rejouant les événements. Chaque fois que l’état d’une entité commerciale change, un nouvel événement est ajouté à la liste des événements. Étant donné que l’enregistrement d’un événement est une opération unique, elle est intrinsèquement atomique.

Pour voir comment fonctionne l’approvisionnement d’événements, considérez l’entité Order comme exemple. Dans une approche traditionnelle, chaque commande correspond à une ligne d'une table ORDER et à des lignes, par exemple, d'une table ORDER_LINE_ITEM. Mais lors de l’utilisation de l’approvisionnement d’événements, le service de commande stocke une commande sous la forme de ses événements de changement d’état : Créé, approuvé, expédié, annulé. Chaque événement contient suffisamment de données pour reconstituer l’état de l’Ordre.

Dans une architecture de microservices, obtenez l'atomicité grâce à l'approvisionnement d'événements

Les événements persistent dans un magasin d’événements, qui est une base de données d’événements. Le magasin dispose d’une API pour ajouter et récupérer les événements d’une entité. L'Event Store se comporte également comme le Message Broker dans les architectures que nous avons décrites précédemment. Il fournit une API qui permet aux services de s'abonner à des événements. L'Event Store livre tous les événements à tous les abonnés intéressés. L’Event Store est l’épine dorsale d’une architecture de microservices pilotée par événements.

L’approvisionnement d’événements présente plusieurs avantages. Il résout l’un des problèmes clés de la mise en œuvre d’une architecture pilotée par événements et permet de publier de manière fiable des événements chaque fois que l’état change. Par conséquent, il résout les problèmes de cohérence des données dans une architecture de microservices. De plus, comme il conserve les événements plutôt que les objets de domaine, il évite en grande partie le problème d’inadéquation de l’impédance relationnelle objet . L'approvisionnement d'événements fournit également un journal d'audit 100 % fiable des modifications apportées à une entité commerciale et permet de mettre en œuvre des requêtes temporelles qui déterminent l'état d'une entité à tout moment. Un autre avantage majeur de l’approvisionnement d’événements est que votre logique métier se compose d’entités métier faiblement couplées qui échangent des événements. Cela facilite grandement la migration d’une application monolithique vers une architecture de microservices.

L’approvisionnement d’événements présente également certains inconvénients. Il s’agit d’un style de programmation différent et inconnu, il y a donc une courbe d’apprentissage. Le magasin d'événements prend uniquement en charge directement la recherche d'entités commerciales par clé primaire. Vous devez utiliser la séparation des responsabilités des requêtes de commande (CQRS) pour implémenter les requêtes. Par conséquent, les applications doivent gérer des données finalement cohérentes.

Résumé

Dans une architecture de microservices, chaque microservice possède son propre magasin de données privé. Différents microservices peuvent utiliser différentes bases de données SQL et NoSQL. Bien que cette architecture de base de données présente des avantages significatifs, elle crée certains défis en matière de gestion des données distribuées. Le premier défi consiste à mettre en œuvre des transactions commerciales qui maintiennent la cohérence entre plusieurs services. Le deuxième défi est de savoir comment mettre en œuvre des requêtes qui récupèrent des données à partir de plusieurs services.

Pour de nombreuses applications, la solution consiste à utiliser une architecture pilotée par événements. L’un des défis liés à la mise en œuvre d’une architecture pilotée par événements est de savoir comment mettre à jour l’état de manière atomique et comment publier les événements. Il existe plusieurs façons d’y parvenir, notamment en utilisant la base de données comme file d’attente de messages, en explorant les journaux de transactions et en recherchant des événements.

Dans les prochains articles de blog, nous continuerons à approfondir d’autres aspects des microservices.

Rédacteur – Cette série d’articles en sept parties est désormais terminée :

  1. Introduction aux microservices
  2. Création de microservices : Utilisation d'une passerelle API
  3. Création de microservices : Communication inter-processus dans une architecture de microservices
  4. Découverte de services dans une architecture de microservices
  5. Gestion des données pilotée par les événements pour les microservices (cet article)
  6. Choisir une stratégie de déploiement de microservices
  7. Refactorisation d'un monolithe en microservices

Vous pouvez également télécharger l'ensemble complet des articles, ainsi que des informations sur la mise en œuvre de microservices à l'aide de NGINX Plus, sous forme d'ebook – Microservices : De la conception au déploiement . Et consultez notre série sur l’ architecture de référence des microservices et la page Solutions de microservices .

Le blogueur invité Chris Richardson est le fondateur du CloudFoundry.com original, un PaaS (Platform as a Service) Java précoce pour Amazon EC2. Il conseille désormais les organisations pour améliorer la manière dont elles développent et déploient des applications. Il blogue également régulièrement sur les microservices sur http://microservices.io .


« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."