Modifier

Partager via


Minimiser la coordination

Stockage Azure
Azure SQL Database
Azure Cosmos DB

Minimiser la coordination pour garantir la scalabilité

La plupart des applications cloud sont constituées de plusieurs services d’application : frontends web, bases de données, processus métier, rapport et analyse, etc. Pour assurer le niveau de scalabilité et de fiabilité attendu, chacun de ces services doit s’exécuter sur plusieurs instances.

Les systèmes non coordonnés, où le travail peut être géré indépendamment sans devoir transmettre des messages entre machines, sont généralement plus simples à mettre à l’échelle. La coordination n’est généralement pas un état binaire, mais un spectre. La coordination s'effectue à différents niveaux, tels que les données ou le calcul.

Que se passe-t-il quand deux instances essaient d’effectuer des opérations simultanées qui ont des répercussions sur un état partagé ? Dans certains cas, il doit exister une coordination entre les nœuds, par exemple pour préserver les garanties ACID. Dans ce diagramme, Node2 attend que Node1 libère un verrou de base de données :

Digramme de verrouillage de base de données

La coordination limite les avantages de la mise à l’échelle horizontale et crée des goulots d’étranglement. Dans cet exemple, à mesure que l’application effectue un scale-out et que des instances supplémentaires sont ajoutées, la contention de verrouillage augmente. Dans le pire des cas, les instances frontend passent l’essentiel de leur temps à attendre des verrous.

La sémantique « exactly once » (une seule fois) est une autre source fréquente de coordination. Par exemple, une commande doit être traitée une seule fois. Deux workers sont à l’écoute des nouvelles commandes. Worker1 récupère une commande pour traitement. L’application doit veiller à ce que Worker2 ne duplique pas le travail, mais aussi à ce que la commande ne soit pas supprimée si Worker1 subit une défaillance.

Diagramme de coordination

Vous pouvez utiliser un modèle tel que Superviseur de l'agent du planificateur pour assurer la coordination entre les Workers, mais dans le cas présent, la répartition du travail peut être une meilleure approche. Chaque worker se voit affecter une certaine série de commandes (par exemple, par région de facturation). Si un worker subit une défaillance, une nouvelle instance reprend là où l’instance précédente s’est arrêtée, mais il n’y a pas de contention entre plusieurs instances.

Recommandations

Utilisez des composants découplés qui communiquent de manière asynchrone. Les composants devraient idéalement utiliser des événements pour communiquer entre eux.

Adoptez la cohérence éventuelle. quand les données sont distribuées, une coordination est nécessaire pour mettre en œuvre des garanties de cohérence forte. Par exemple, prenons le cas d’une opération qui met à jour deux bases de données. Au lieu de la placer dans l'étendue d'une transaction unique, il est préférable que le système prenne en charge la cohérence éventuelle, peut-être en utilisant le modèle de transaction de compensation, pour procéder à une restauration logique après une défaillance.

Utilisez des événements de domaine pour synchroniser l'état. Un événement de domaine est un événement qui s'inscrit lorsque quelque chose d'important se produit dans le domaine. Les services intéressés peuvent écouter l’événement, au lieu d’utiliser une transaction globale pour assurer la coordination entre plusieurs services. Si cette approche est utilisée, le système doit tolérer la cohérence éventuelle (voir l’élément précédent).

Envisagez l'utilisation de modèles tels que CQRS et l'approvisionnement en événements (event sourcing). Ces deux modèles peuvent contribuer à réduire la contention entre les charges de travail de lecture et les charges de travail d’écriture.

  • Le modèle CQRS sépare les opérations de lecture des opérations d'écriture. Dans certaines implémentations, les données de lecture sont physiquement séparées des données d’écriture.

  • Dans le modèle d'approvisionnement en événements, les changements d'état sont inscrits comme série d'événements dans une banque de données en ajout seul. L’ajout d’un événement au flux est une opération atomique, nécessitant un verrouillage minimal.

Ces deux modèles se complètent mutuellement. Si la banque en écriture seule de CQRS utilise l’approvisionnement en événements, la banque en lecture seule peut écouter les mêmes événements pour créer un instantané accessible en lecture de l’état actif, optimisé pour les requêtes. Cependant, avant d’adopter CQRS ou l’approvisionnement en événements, prenez connaissance des inconvénients de cette approche.

Répartition des données et état. évitez de placer toutes vos données dans un même schéma de données partagé entre divers services d’application. Une architecture de microservices applique ce principe en rendant chaque service responsable de sa propre banque de données. Dans une base de données unique, le répartition des données dans différentes partitions peut améliorer la concurrence, car un service qui écrit dans une partition n’affecte pas un service qui écrit dans une autre partition. Même si le partitionnement apporte un certain degré de coordination, vous pouvez l'utiliser pour augmenter le parallélisme et améliorer la scalabilité. Partitionner l’état monolithique en blocs plus petits afin que les données puissent être gérées indépendamment.

Concevez des opérations idempotentes. dans la mesure du possible, concevez des opérations idempotent. De cette façon, elles pourront être traitées à l’aide d’une sémantique « at-least-once » (au moins une fois). Par exemple, vous pouvez placer des éléments de travail dans une file d’attente. Si un worker se bloque au milieu d’une opération, un autre travail récupère simplement l’élément de travail. Si le Worker doit mettre à jour des données, ainsi qu’émettre d’autres messages dans le cadre de sa logique, le modèle de traitement du message idempotent doit être utilisé.

Utilisez si possible l'accès concurrentiel optimiste. le contrôle d’accès concurrentiel pessimiste utilise des verrous de base de données pour éviter les conflits. Cela peut dégrader les performances et réduire la disponibilité. Avec le contrôle d’accès concurrentiel optimiste, chaque transaction modifie une copie ou un instantané des données. Une fois la transaction engagée, le moteur de base de données valide la transaction et rejette celles qui affecteraient la cohérence de la base de données.

Azure SQL Database et SQL Server prennent en charge l'accès concurrentiel optimiste via l'isolement d'instantané. Certains services de stockage Azure prennent en charge l'accès concurrentiel optimiste via l'utilisation d'ETags, notamment Azure Cosmos DB et Stockage Azure.

Envisagez d'utiliser MapReduce ou d'autres algorithmes parallèles distribués. Selon les données et le type de travail à effectuer, vous pourrez peut-être répartir le travail en plusieurs tâches indépendantes qui seront effectuées par plusieurs nœuds fonctionnant en parallèle. Consultez Style d'architecture Big Compute.

Utilisez l'élection du responsable pour la coordination. dans les cas où vous avez besoin de coordonner les opérations, veillez à ce que le coordinateur ne devienne pas un point de défaillance unique dans l’application. Avec le modèle d'élection du responsable, seule une instance est responsable à un instant T, et celle-ci joue le rôle de coordinateur. En cas d’échec du responsable, une nouvelle instance est élue responsable.