Enjeux des transactions distribuées dans le cadre d'une architecture de microservice

Bonjour. Déjà en septembre, OTUS ouvre un set pour un nouveau groupe du cours "Highload Architect" . À cet égard, je continue une série de mes publications écrites spécifiquement pour ce cours, et je vous invite également à mon webinaire gratuit, dans lequel je vous parlerai en détail du programme de cours et du format de la formation en OTUS. Vous pouvez vous inscrire au webinaire ici .








introduction



Comme vous le savez, le passage d'une architecture monolithe à une architecture de microservices entraîne un certain nombre de difficultés liées à la fois à la partie technique du projet et au facteur humain. L'un des défis techniques les plus difficiles est d'assurer la cohérence dans un système distribué.



Cohérence



Un point assez subtil est que la cohérence dans le contexte des systèmes distribués est différente de la cohérence dans le contexte des bases de données. De plus, par cohérence, nous entendrons exactement la première: une opération incomplète (erronée) n'introduit aucun effet et ne modifie pas les données; avec un accès simultané aux données, toutes les opérations sont considérées comme atomiques (vous ne pouvez pas voir le résultat intermédiaire de l'opération) si les données ont plusieurs copies (réplication) , alors la séquence des opérations d'application sur toutes les copies est la même. En fait, nous voulons recevoir une transaction ACID, mais seulement une transaction distribuée.



La cause du problème



Pourquoi est-il difficile de maintenir la cohérence dans une architecture de microservice? Le fait est que ce style architectural implique souvent l'utilisation de la base de données par modèle de service. Permettez-moi de vous rappeler que ce modèle consiste dans le fait que chaque microservice a sa ou ses propres bases (bases, car en plus de la source de données principale, par exemple, un cache peut être utilisé). Cette approche permet, d'une part, de ne pas ajouter de liens de format de données implicites entre les microservices (les microservices n'interagissent que explicitement via l'API), d'autre part, de tirer le meilleur parti d'un tel avantage de l'architecture de microservice en tant que technologie indépendante (nous pouvons choisir la technologie de stockage de données qui convient à la charge particulière sur le microservice) ). Mais avec tout cela, nous avons perdu la garantie de cohérence des données. Jugez par vous-mêmele monolithe communiquait avec une grande base de données, qui permettait de fournir des transactions ACID. Maintenant, il existe de nombreuses bases de données, et au lieu d'une seule grosse transaction ACID, nous avons beaucoup de petites transactions ACID. Notre tâche sera de combiner toutes ces transactions en une seuledistribué .



Cohérence optimiste



La première chose qui nous vient à l'esprit est le concept de cohérence optimiste: nous commettons autant de transactions que nous le souhaitons pour autant de moteurs de stockage que nécessaire. En même temps, nous nous attendons à ce que tout se passe bien, et si tout va mal, alors nous disons que tout ira bien à la fin. Si tout va mal à la fin, alors nous disons: "Oui, cela arrive, mais avec une probabilité extrêmement faible."



Blague à part, négliger la cohérence quand ce n'est pas critique pour l'entreprise est une bonne idée, surtout si l'on considère les efforts que cela nous coûtera pour la maintenir (ce que j'espère que vous verrez un peu plus tard).



Options de cohérence



Si la cohérence est essentielle pour l'entreprise, il existe plusieurs façons d'essayer d'y parvenir. Si nous parlons d'une situation où les données sont mises à jour par un service (par exemple, il existe une réplication de base de données), des algorithmes de cohérence standard tels que Paxos ou Raft peuvent être appliqués. De telles transactions sont dites homogènes . Si les données sont mises à jour par plusieurs services (c'est-à-dire qu'une transaction hétérogène a lieu ), comment commence la complexité, dont nous avons parlé ci-dessus.



D'une part, nous pouvons encore contourner le besoin de fournir une transaction distribuée en recherchant une architecture basée sur les services (nous combinons les services de manière à ce que la transaction soit homogène). Une telle solution n'est pas très canonique du point de vue des principes de l'architecture des microservices, mais elle est techniquement beaucoup plus simple, c'est pourquoi elle est souvent utilisée dans la pratique. Par contre, on peut sortir des microservices canoniques, mais en même temps appliquer l'un des mécanismes pour assurer des transactions distribuées: un commit en deux phases ou une saga. Cet article explorera la première option et discutera de la seconde la prochaine fois.



Engagement en deux phases



Le mécanisme est extrêmement simple: il existe un gestionnaire de transactions qui orchestre réellement la transaction. Lors de la première étape (préparation), le gestionnaire de transactions émet la commande appropriée pour les gestionnaires de ressources, selon laquelle ils écrivent des données dans leurs journaux qui seront validées. Après avoir reçu la confirmation de tous les gestionnaires de ressources de la réussite de la première étape, le gestionnaire de transactions démarre la deuxième étape et émet la commande suivante (validation), selon laquelle les gestionnaires de ressources appliquent les modifications précédemment acceptées.



Malgré son apparente simplicité, cette approche présente un certain nombre d'inconvénients. Premièrement, si au moins un gestionnaire de ressources échoue dans la deuxième phase, la transaction entière doit être annulée. Ainsi, l'un des principes de l'architecture des microservices est violé - la résistance aux pannes (quand nous sommes arrivés à un système distribué, nous avons immédiatement supposé que l'échec est la norme et non une situation exceptionnelle). De plus, s'il y a beaucoup d'échecs (et il y en aura beaucoup), le processus d'annulation des transactions devra être automatisé (y compris l'écriture de transactions qui annulent les transactions). Deuxièmement, le gestionnaire de transactions lui-même est un point de défaillance unique. Il doit être capable d'émettre de manière transactionnelle des id-shniks aux transactions. Troisièmement, puisque des commandes spéciales sont données au référentiel, il est logique de supposer que le référentiel devrait pouvoir le faire,c'est-à-dire conforme à la norme XA, et toutes les technologies modernes ne la respectent pas (les courtiers tels que Kafka, RabbitMQ et les solutions NoSQL telles que MongoDB et Cassandra ne prennent pas en charge les commits en deux phases).



La conclusion qui se dégage de tous ces facteurs a été parfaitement articulée par Chris Richardson: "2PC pas une option" (la validation en deux phases n'est pas une option).



Production



Nous avons compris pourquoi les transactions distribuées sont la principale douleur technique d'une architecture de microservices et avons parlé de diverses options pour résoudre ce problème, discuté en détail du mécanisme de validation en deux phases.






J'invite tout le monde à s'inscrire à mon webinaire sur le cours , dans lequel je vous expliquerai en détail le format de la formation et présenterai à chacun le programme de formation.






Lire la suite:






All Articles