Partager via


Modèle d’élection des dirigeants

Stockage Blob Azure

Coordonnez les actions effectuées par une collection d’instances de collaboration dans une application distribuée en choisissant une instance comme responsable qui assume la responsabilité de la gestion des autres. Cela peut aider à s’assurer que les instances ne sont pas en conflit les unes avec les autres, provoquent la contention des ressources partagées ou interfèrent par inadvertance avec le travail que d’autres instances effectuent.

Contexte et problème

Une application cloud classique a de nombreuses tâches agissant de manière coordonnée. Ces tâches peuvent toutes être des instances exécutant le même code et nécessitant l’accès aux mêmes ressources, ou elles peuvent fonctionner ensemble en parallèle pour effectuer les parties individuelles d’un calcul complexe.

Les instances de tâche peuvent s’exécuter séparément pendant une grande partie du temps, mais il peut également être nécessaire de coordonner les actions de chaque instance pour s’assurer qu’elles ne sont pas en conflit, provoquer des conflits pour les ressources partagées ou interférer accidentellement avec le travail effectué par d’autres instances de tâche.

Par exemple:

  • Dans un système cloud qui implémente la mise à l’échelle horizontale, plusieurs instances de la même tâche peuvent s’exécuter en même temps avec chaque instance servant un utilisateur différent. Si ces instances écrivent dans une ressource partagée, il est nécessaire de coordonner leurs actions pour empêcher chaque instance de remplacer les modifications apportées par les autres.
  • Si les tâches effectuent des éléments individuels d’un calcul complexe en parallèle, les résultats doivent être agrégés lorsqu’ils se terminent tous.

Les instances de tâche sont tous des pairs. Il n’existe donc pas de leader naturel qui peut agir comme coordinateur ou agrégateur.

Solution

Une instance de tâche unique doit être élue pour agir en tant que responsable, et cette instance doit coordonner les actions des autres instances de tâche subordonnées. Si toutes les instances de tâche exécutent le même code, elles sont chacune capables d’agir en tant que leader. Par conséquent, le processus électoral doit être géré avec soin pour empêcher deux instances ou plus de prendre le poste de leader en même temps.

Le système doit fournir un mécanisme robuste pour sélectionner le leader. Cette méthode doit faire face à des événements tels que des pannes réseau ou des échecs de processus. Dans de nombreuses solutions, les instances de tâche subordonnées surveillent le leader via un type de méthode de pulsation ou par interrogation. Si le leader désigné se termine de façon inattendue ou si un échec réseau rend le responsable indisponible pour les instances de tâche subordonnées, il est nécessaire qu’ils choisissent un nouveau leader.

Il existe plusieurs stratégies pour choisir un leader parmi un ensemble de tâches dans un environnement distribué, notamment :

  • Course pour acquérir un mutex partagé et distribué. La première instance de tâche qui acquiert le mutex est le leader. Toutefois, le système doit s’assurer que, si le leader se termine ou devient déconnecté du reste du système, le mutex est libéré pour permettre à une autre instance de tâche de devenir le leader. Cette stratégie est illustrée dans l’exemple suivant.
  • Implémentation de l’un des algorithmes d’élection de leader courants tels que l’algorithme d’intimidation, l’algorithme de consensus Raft ou l’algorithme ring. Ces algorithmes supposent que chaque candidat de l’élection a un ID unique et qu’il peut communiquer avec les autres candidats de manière fiable.

Problèmes et considérations

Tenez compte des points suivants lors de la décision d’implémenter ce modèle :

  • Le processus d’élection d’un leader doit être résilient aux défaillances temporaires et persistantes.
  • Il doit être possible de détecter quand le leader a échoué ou est devenu indisponible (par exemple en raison d’un échec de communication). La détection rapide nécessaire dépend du système. Certains systèmes peuvent fonctionner pendant une courte période sans leader, pendant lequel une erreur temporaire peut être corrigée. Dans d’autres cas, il peut être nécessaire de détecter immédiatement l’échec du leader et de déclencher une nouvelle élection.
  • Dans un système qui implémente la mise à l’échelle automatique horizontale, le leader peut être arrêté si le système effectue un scale-back et arrête certaines des ressources informatiques.
  • L’utilisation d’un mutex distribué partagé introduit une dépendance sur le service externe qui fournit le mutex. Le service constitue un point de défaillance unique. S’il devient indisponible pour une raison quelconque, le système ne sera pas en mesure d’élire un leader.
  • L’utilisation d’un processus dédié unique en tant que leader est une approche simple. Toutefois, si le processus échoue, il peut y avoir un délai important pendant son redémarrage. La latence résultante peut affecter les performances et les temps de réponse d’autres processus s’ils attendent que le responsable coordonne une opération.
  • L’implémentation manuelle d’un des algorithmes d’élection de leader offre la plus grande flexibilité pour le réglage et l’optimisation du code.
  • Évitez de faire du leader un goulot d’étranglement dans le système. L’objectif du leader est de coordonner le travail des tâches subordonnées, et il n’est pas nécessairement nécessaire de participer à ce travail lui-même, bien qu’il puisse le faire si la tâche n’est pas élue en tant que leader.

Quand utiliser ce modèle

Utilisez ce modèle lorsque les tâches d’une application distribuée, telles qu’une solution hébergée dans le cloud, ont besoin d’une coordination minutieuse et qu’il n’existe aucun leader naturel.

Ce modèle peut ne pas être utile si :

  • Il existe un leader naturel ou un processus dédié qui peut toujours agir en tant que leader. Par exemple, il peut être possible d’implémenter un processus singleton qui coordonne les instances de tâche. Si ce processus échoue ou devient défectueux, le système peut l’arrêter et le redémarrer.
  • La coordination entre les tâches peut être obtenue à l’aide d’une méthode plus légère. Par exemple, si plusieurs instances de tâche ont simplement besoin d’un accès coordonné à une ressource partagée, une meilleure solution consiste à utiliser un verrouillage optimiste ou pessimiste pour contrôler l’accès.
  • Une solution tierce, comme Apache Zookeeper , peut être une solution plus efficace.

Conception de la charge de travail

Un architecte doit évaluer la façon dont le modèle d’élection de leader peut être utilisé dans la conception de leur charge de travail pour répondre aux objectifs et principes abordés dans les piliers Azure Well-Architected Framework. Par exemple:

Pilier Comment ce modèle soutient les objectifs des piliers.
Les décisions relatives à la fiabilité contribuent à rendre votre charge de travail résiliente aux dysfonctionnements et à s’assurer qu’elle retrouve un état de fonctionnement optimal après une défaillance. Ce modèle atténue l’effet des dysfonctionnements de nœud en redirigeant de manière fiable le travail. Il implémente également le basculement par le biais d’algorithmes de consensus lorsqu’un leader ne fonctionne pas correctement.

- RE :05 Redondance
- RE :07 Auto-guérison

Comme pour toute autre décision de conception, il convient de prendre en compte les compromis par rapport aux objectifs des autres piliers qui pourraient être introduits avec ce modèle.

Exemple :

L’exemple Leader Election sur GitHub montre comment utiliser un bail sur un objet blob stockage Azure pour fournir un mécanisme d’implémentation d’un mutex partagé et distribué. Ce mutex peut être utilisé pour choisir un leader parmi un groupe d’instances de travail disponibles. La première instance à acquérir le bail est élue responsable et reste le leader jusqu’à ce qu’il libère le bail ou n’est pas en mesure de renouveler le bail. D’autres instances de travail peuvent continuer à surveiller le bail d’objet blob si le leader n’est plus disponible.

Un bail d’objet blob est un verrou d’écriture exclusif sur un objet blob. Un objet blob unique peut être l’objet d’un seul bail à tout moment. Une instance de worker peut demander un bail sur un objet blob spécifié, et elle sera accordée au bail si aucune autre instance de worker n’a un bail sur le même objet blob. Sinon, la requête lève une exception.

Pour éviter qu’une instance de leader défaillante conserve indéfiniment le bail, spécifiez une durée de vie pour le bail. À l’expiration, le bail devient disponible. Toutefois, alors qu’une instance conserve le bail, elle peut demander que le bail soit renouvelé et qu’il sera accordé le bail pendant une période supplémentaire. L’instance de leader peut répéter continuellement ce processus s’il souhaite conserver le bail. Pour plus d’informations sur la façon de louer un objet blob, consultez l’api REST (Lease Blob).

La BlobDistributedMutex classe de l’exemple C# ci-dessous contient la RunTaskWhenMutexAcquired méthode qui permet à une instance de worker de tenter d’acquérir un bail sur un objet blob spécifié. Les détails de l’objet blob (le nom, le conteneur et le compte de stockage) sont transmis au constructeur dans un BlobSettings objet lorsque l’objet BlobDistributedMutex est créé (cet objet est un struct simple inclus dans l’exemple de code). Le constructeur accepte également un Task code qui fait référence au code que l’instance de worker doit exécuter s’il acquiert correctement le bail sur l’objet blob et est élu responsable. Le code qui gère les détails de bas niveau d’acquisition du bail est implémenté dans une classe d’assistance distincte nommée BlobLeaseManager.

public class BlobDistributedMutex
{
  ...
  private readonly BlobSettings blobSettings;
  private readonly Func<CancellationToken, Task> taskToRunWhenLeaseAcquired;
  ...

  public BlobDistributedMutex(BlobSettings blobSettings,
           Func<CancellationToken, Task> taskToRunWhenLeaseAcquired, ... )
  {
    this.blobSettings = blobSettings;
    this.taskToRunWhenLeaseAcquired = taskToRunWhenLeaseAcquired;
    ...
  }

  public async Task RunTaskWhenMutexAcquired(CancellationToken token)
  {
    var leaseManager = new BlobLeaseManager(blobSettings);
    await this.RunTaskWhenBlobLeaseAcquired(leaseManager, token);
  }
  ...

La RunTaskWhenMutexAcquired méthode de l’exemple de code précédent appelle la RunTaskWhenBlobLeaseAcquired méthode indiquée dans l’exemple de code suivant pour acquérir réellement le bail. La RunTaskWhenBlobLeaseAcquired méthode s’exécute de manière asynchrone. Si le bail est acquis avec succès, l’instance de travail a été élue responsable. L’objectif du taskToRunWhenLeaseAcquired délégué est d’effectuer le travail qui coordonne les autres instances de travail. Si le bail n’est pas acquis, une autre instance de travail a été élue en tant que responsable et l’instance de travail actuelle reste subordonné. Notez que la TryAcquireLeaseOrWait méthode est une méthode d’assistance qui utilise l’objet BlobLeaseManager pour acquérir le bail.

  private async Task RunTaskWhenBlobLeaseAcquired(
    BlobLeaseManager leaseManager, CancellationToken token)
  {
    while (!token.IsCancellationRequested)
    {
      // Try to acquire the blob lease.
      // Otherwise wait for a short time before trying again.
      string? leaseId = await this.TryAcquireLeaseOrWait(leaseManager, token);

      if (!string.IsNullOrEmpty(leaseId))
      {
        // Create a new linked cancellation token source so that if either the
        // original token is canceled or the lease can't be renewed, the
        // leader task can be canceled.
        using (var leaseCts =
          CancellationTokenSource.CreateLinkedTokenSource(new[] { token }))
        {
          // Run the leader task.
          var leaderTask = this.taskToRunWhenLeaseAcquired.Invoke(leaseCts.Token);
          ...
        }
      }
    }
    ...
  }

La tâche démarrée par le responsable s’exécute également de manière asynchrone. Pendant que cette tâche est en cours d’exécution, la RunTaskWhenBlobLeaseAcquired méthode indiquée dans l’exemple de code suivant tente régulièrement de renouveler le bail. Cela permet de s’assurer que l’instance de travail reste le leader. Dans l’exemple de solution, le délai entre les demandes de renouvellement est inférieur au temps spécifié pour la durée du bail afin d’empêcher l’élection d’une autre instance de travail. Si le renouvellement échoue pour une raison quelconque, la tâche spécifique au responsable est annulée.

Si le bail ne parvient pas à être renouvelé ou si la tâche est annulée (éventuellement en raison de l’arrêt de l’instance de travail), le bail est libéré. À ce stade, cette instance de travail ou une autre instance de travail peut être élue en tant que leader. L’extrait de code suivant montre cette partie du processus.

  private async Task RunTaskWhenBlobLeaseAcquired(
    BlobLeaseManager leaseManager, CancellationToken token)
  {
    while (...)
    {
      ...
      if (...)
      {
        ...
        using (var leaseCts = ...)
        {
          ...
          // Keep renewing the lease in regular intervals.
          // If the lease can't be renewed, then the task completes.
          var renewLeaseTask =
            this.KeepRenewingLease(leaseManager, leaseId, leaseCts.Token);

          // When any task completes (either the leader task itself or when it
          // couldn't renew the lease) then cancel the other task.
          await CancelAllWhenAnyCompletes(leaderTask, renewLeaseTask, leaseCts);
        }
      }
    }
  }
  ...
}

La KeepRenewingLease méthode est une autre méthode d’assistance qui utilise l’objet BlobLeaseManager pour renouveler le bail. La CancelAllWhenAnyCompletes méthode annule les tâches spécifiées comme les deux premiers paramètres. Le diagramme suivant illustre l’utilisation de la BlobDistributedMutex classe pour choisir un leader et exécuter une tâche qui coordonne les opérations.

La figure 1 illustre les fonctions de la classe BlobDistributedMutex

L’exemple de code suivant montre comment utiliser la BlobDistributedMutex classe au sein d’une instance worker. Ce code acquiert un bail sur un objet blob nommé MyLeaderCoordinatorTask dans le stockage Blob Azure conteneur du bail, et spécifie que le code défini dans la MyLeaderCoordinatorTask méthode doit s’exécuter si l’instance worker est élue responsable.

// Create a BlobSettings object with the connection string or managed identity and the name of the blob to use for the lease
BlobSettings blobSettings = new BlobSettings(storageConnStr, "leases", "MyLeaderCoordinatorTask");

// Create a new BlobDistributedMutex object with the BlobSettings object and a task to run when the lease is acquired
var distributedMutex = new BlobDistributedMutex(
    blobSettings, MyLeaderCoordinatorTask);

// Wait for completion of the DistributedMutex and the UI task before exiting
await distributedMutex.RunTaskWhenMutexAcquired(cancellationToken);

...

// Method that runs if the worker instance is elected the leader
private static async Task MyLeaderCoordinatorTask(CancellationToken token)
{
  ...
}

Notez les points suivants concernant l’exemple de solution :

  • L’objet blob est un point de défaillance unique potentiel. Si le service blob devient indisponible ou est inaccessible, le leader ne pourra pas renouveler le bail et aucune autre instance de travail ne pourra acquérir le bail. Dans ce cas, aucune instance de travail ne pourra agir en tant que leader. Toutefois, le service d’objets blob est conçu pour être résilient, de sorte que l’échec complet du service d’objets blob est considéré comme extrêmement peu probable.
  • Si la tâche effectuée par le leader se bloque, le leader peut continuer à renouveler le bail, empêchant toute autre instance de travail d’acquérir le bail et de prendre le poste de leader afin de coordonner les tâches. Dans le monde réel, la santé du leader doit être vérifiée à intervalles fréquents.
  • Le processus électoral n’est pas déterministe. Vous ne pouvez pas faire d’hypothèses sur l’instance worker qui acquiert le bail d’objet blob et devient le leader.
  • L’objet blob utilisé comme cible du bail d’objet blob ne doit pas être utilisé à d’autres fins. Si une instance de worker tente de stocker des données dans cet objet blob, ces données ne seront pas accessibles, sauf si l’instance worker est le leader et contient le bail d’objet blob.

Étapes suivantes

Les conseils suivants peuvent également être pertinents lors de l’implémentation de ce modèle :