Je suis relativement nouveau dans l'architecture de microservices. Nous avons une application Web de taille moyenne et je pèse les avantages et les inconvénients de la décomposer en microservices au lieu d'un système monolithique que nous avons maintenant aller de l'avant.
Pour autant que je le comprends, considérons les microservices A
et B
, chacun reposant sur un sous-ensemble de données que l'autre possède. Si un message est publié par A
disant que quelque chose a changé, B
peut consommer ce message et répliquer une copie locale des informations de A
et l'utiliser pour faire ce que B
doit faire.
Cependant, que se passe-t-il si B
tombe/échoue et après un certain temps, revient à nouveau. Pendant ce temps d'arrêt, A
a publié deux autres messages. Comment B
sait-il comment mettre à jour sa copie locale des informations de A
?
Certes, si B
est le seul consommateur de la file d'attente de A
, il peut commencer à la lire une fois qu'il sera de retour en ligne, mais que se passe-t-il s'il y a d'autres consommateurs de cette file d'attente et que ces messages sont consommés?
Comme exemple plus concret, si un service Users
a son adresse e-mail mise à jour alors qu'un microservice Billing
est en panne, si le microservice Billing
revient, comment sait-il que l'e-mail a été mis à jour?
Lorsque les microservices reviennent, est-ce que cela fait une émission disant "Hey, je suis de retour, donnez-moi toutes vos informations actuelles?"
En général, quelles seraient les meilleures pratiques de l'industrie pour la synchronisation des données?
Après avoir fait un peu plus de recherche, je suis tombé sur cet article dont j'ai tiré quelques citations qui, je pense, sont utiles pour ce que je veux accomplir (et pour tous les futurs lecteurs). Cela offre un moyen d'adopter un modèle de programmation réactive par rapport à un modèle de programmation impératif.
Recherche d'événements
L'idée ici est de représenter la transition d'état de chaque application sous la forme d'un événement immuable. Les événements sont ensuite stockés sous forme de journal ou de journal au fur et à mesure qu’ils se produisent (également appelés "magasin d’événements"). Ils peuvent également être interrogés et stockés indéfiniment, dans le but de représenter comment l’état de l’application, dans son ensemble, a évolué au fil du temps.
Ce que cela permet d'accomplir, c'est que si un microservice tombe en panne, alors que d'autres événements qui le concernent sont publiés et les événements sont consommés par, disons, d'autres instances de ce microservice, lorsque ce microservice revient, il peut faire référence à ce event store
pour récupérer tous les événements qui lui ont manqué pendant la période de panne.
Apache Kafka en tant que courtier d'événements
Envisagez l'utilisation d'Apache Kafka qui peut stocker et distribuer des milliers d'événements par seconde et dispose de mécanismes de réplication et de tolérance aux pannes intégrés. Il dispose d'un stockage permanent des événements qui peuvent être stockés sur le disque indéfiniment et consommé à tout moment (mais pas supprimé) du sujet (la file d'attente de fantaisie de Kafka) ont été livrés à.
Les événements sont ensuite affectés à des décalages qui les identifient de manière univoque dans le sujet - Kafka peut gérer les décalages lui-même, fournissant facilement une sémantique de livraison "au plus une fois" ou "au moins une fois", mais ils peuvent également être négociés lorsqu'un consommateur d'événements rejoint un sujet. , permettant aux microservices de commencer à consommer des événements à partir de n'importe quel endroit arbitraire dans le temps - généralement là où le consommateur s'est arrêté. Si le dernier décalage d'événement consommé persiste de manière transactionnelle dans le stockage local des services lorsque les cas d'utilisation "se terminent avec succès", ce décalage peut facilement être utilisé pour obtenir une sémantique de livraison d'événement "une seule fois".
En fait, lorsque les consommateurs s'identifient à Kafka, Kafka enregistrera les messages qui ont été livrés à quel consommateur afin qu'il ne les serve plus.
Sagas
Pour les cas d'utilisation plus complexes où la communication entre différents services est en effet nécessaire, la responsabilité de terminer le cas d'utilisation doit être bien reconnue - le cas d'utilisation est décentralisé et ne se termine que lorsque tous les services impliqués reconnaissent que leur tâche a été menée à bien, sinon l'ensemble du cas d'utilisation doit échouer. et des mesures correctives doivent être déclenchées pour annuler tout état local non valide.
C'est alors que la saga entre en jeu. Une saga est une séquence de transactions locales. Chaque transaction locale met à jour la base de données et publie un message ou un événement pour déclencher la prochaine transaction locale dans la saga. Si une transaction locale échoue car elle viole une règle commerciale, la saga exécute une série de transactions compensatoires qui annulent les modifications apportées par les transactions locales précédentes. Lisez ceci pour plus d'informations.
Je contesterais toute votre idée de "pousser les données vers tous les autres microservices".
Habituellement, si un service de facturation a besoin d'une adresse e-mail, il demande simplement au service d'adresse l'adresse e-mail du client spécifique. Il n'a pas besoin de conserver une copie de toutes les données d'adresse et ne sera pas informé en cas de changement. Il demande simplement et obtient la réponse des données les plus récentes.
Même si je suis en retard, je voudrais mettre mes 2 cents sur l'argument car je pense que c'est un point important lorsque vous souhaitez évaluer la conception d'une architecture de microservices événementielle. Chaque microservice sait exactement quels sont les événements qui ont un impact sur son état et est capable de les attendre. Lorsque le microservice n'est pas disponible, il doit y avoir un composant qui conserve les messages nécessaires du microservice défaillant jusqu'à ce qu'il ne soit pas en mesure de les "consommer". Il s'agit en fait d'un modèle "producteur/consommateur" et non d'un modèle "publier/souscrire". Les courtiers de messages (comme Kafka, RabbitMQ, ActiveMQ, etc.) sont généralement le meilleur moyen d'obtenir ce comportement (à moins que vous n'implémentiez pas quelque chose de différent comme le sourcing d'événements) en fournissant des files d'attente persistantes et un mécanisme ack/nack.
Maintenant, le microservice sait qu'un message est finalement délivré, mais ce n'est pas suffisant: quelle est la façon dont il attend la livraison d'un seul message? peut-il gérer la livraison de plusieurs copies de la même notification d'événement? C'est une question de livraison sémantique (au moins une fois, exactement une fois)
Dernières pensées):
Lorsque vous ajoutez un microservice à votre architecture qui doit consommer les événements des autres, vous devez effectuer la première synchronisation
Même le courtier peut échouer, dans ce cas, les messages sont perdus
pour les deux scénarios, il serait utile de disposer de mécanismes simples pour réhydrater votre état de microservice. Cela pourrait être A REST API ou un script qui envoie des messages, mais la chose la plus importante est d'avoir des moyens d'effectuer une tâche de maintenance
Vous pouvez remplacer une file d'attente d'événements normale par un modèle éditeur/abonné, où le service A
publie un nouveau message de sujet T et B
le type de microservices s'abonnerait au même sujet.
Idéalement, B
serait un service sans état et utiliserait un service de persistance détaché, de telle sorte qu'une instance de service B
ayant échoué serait remplacée par la génération d'une ou de plusieurs instances de service B
de poursuivre son travail, en lisant à partir du même service de persistance partagé.
Si un message est publié par A disant que quelque chose a changé, B peut consommer ce message et répliquer une copie locale des informations de A et l'utiliser pour faire tout ce que B doit faire.
Si vous vouliez que B puisse accéder aux données internes de A, vous feriez mieux de lui donner simplement accès aux bases de données internes de A.
Cependant, vous ne devriez pas faire cela, le point essentiel d'une architecture orientée service est que le service B ne peut pas voir l'état interne du service A et est limité à effectuer des requêtes via les API REST (et vice versa).
Dans votre cas, vous pourriez avoir un service de données utilisateur, qui a la responsabilité de stocker toutes les données utilisateur. D'autres services qui souhaitent utiliser ces données ne le demandent que lorsqu'ils en ont besoin et n'en conservent pas de copie locale (ce qui est vraiment utile si vous pensez à la conformité au RGPD). Le service de données utilisateur peut prendre en charge des opérations CRUD simples comme "Créer un nouvel utilisateur" ou "Changer le nom pour user_id 23" ou il peut avoir des opérations plus complexes, "Trouver tous les utilisateurs standard avec un anniversaire à venir dans les 2 prochaines semaines et leur donner statut d'essai premium ". Désormais, lorsque votre service de facturation doit envoyer un e-mail à l'utilisateur 42, il demande au service de données utilisateur "Quelle est l'adresse e-mail de user_id 42", utilise ses données internes avec toutes les informations de facturation pour créer l'e-mail, puis peut transmettre le message adresse e-mail et corps d'un serveur de messagerie.