Selectie van leider-patroon

Azure Blob Storage

Gebruik dit patroon om de acties te coördineren die worden uitgevoerd door een verzameling samenwerkende exemplaren in een gedistribueerde toepassing door één exemplaar te selecteren als de leider, die vervolgens de verantwoordelijkheid krijgt voor het beheren van de andere exemplaren. Dit kan ertoe bijdragen dat exemplaren niet met elkaar conflicteren, bijvoorbeeld over gedeelde bronnen, of onbedoeld ingrijpen op het werk dat door andere exemplaren wordt uitgevoerd.

Context en probleem

Een gemiddelde cloudtoepassing heeft veel taken die op een gecoördineerde manier samenwerken. Deze taken kunnen allemaal exemplaren zijn die dezelfde code uitvoeren en die toegang tot dezelfde resources vereisen. Een andere mogelijkheid is dat ze gelijktijdig worden uitgevoerd en de afzonderlijke onderdelen van een complexe berekening voor hun rekening nemen.

De taakexemplaren kunnen gedurende een groot deel van de tijd afzonderlijk worden uitgevoerd, maar het kan ook nodig zijn om de acties van elk exemplaar te coördineren om ervoor te zorgen dat ze geen conflict veroorzaken, conflicten veroorzaken voor gedeelde resources of per ongeluk interfereren met het werk dat andere taakexemplaren uitvoeren.

Voorbeeld:

  • In een cloudsysteem met ondersteuning voor horizontaal schalen, kunnen meerdere exemplaren van dezelfde taak tegelijkertijd worden uitgevoerd, waarbij elk exemplaar een andere gebruiker bedient. Als deze exemplaren gegevens wegschrijven naar een gedeelde resource, is het nodig om hun acties te coördineren om te voorkomen dat een exemplaar de wijzigingen van andere exemplaren overschrijft.
  • Als de taken op hetzelfde moment afzonderlijke elementen van een complexe berekening uitvoeren, moeten de resultaten worden geaggregeerd als alle taken zijn voltooid.

De taakexemplaren zijn allemaal gelijkwaardig, dus er is niet een natuurlijke leider die als coördinator of aggregator kan optreden.

Oplossing

Er moet één taakexemplaar worden geselecteerd dat optreedt als de leider, en dit exemplaar moet de acties van de andere, onderliggende taakexemplaren coördineren. Als alle taakexemplaren dezelfde code uitvoeren, zijn ze allemaal geschikt om als leider te fungeren. Daarom moet het verkiezingsproces zorgvuldig worden beheerd om te voorkomen dat twee of meer instanties tegelijkertijd de leiderpositie overnemen.

Het systeem moet een krachtig mechanisme bieden voor het selecteren van de leider. Deze methode moet omgaan met gebeurtenissen zoals netwerkstoringen of fouten in het proces. In veel oplossingen controleren de onderliggende taakexemplaren de leider via een bepaald type heartbeat-methode, of door polling. Als de aangewezen leider onverwacht uitvalt, of een netwerkstoring tot gevolg heeft dat de leider niet meer beschikbaar is voor de onderliggende taakexemplaren, moeten de andere exemplaren een nieuwe leider selecteren.

Er zijn verschillende strategieën voor het selecteren van een leider uit een set taken in een gedistribueerde omgeving, zoals:

  • Het taakexemplaar met de laagst scorende exemplaar- of proces-id selecteren.
  • Het exemplaar selecteren dat als eerste een gedeelde, gedistribueerde mutex verkrijgt. Het eerste taakexemplaar dat de mutex verkrijgt, is de nieuwe leider. Als de leider uitvalt of niet meer kan communiceren met de rest van het systeem, moet het systeem ervoor zorgen dat de mutex wordt vrijgegeven zodat een ander taakexemplaar de leider kan worden.
  • De implementatie van een van de algemene algoritmen voor het selecteren van een leider, zoals het Bully-algoritme of het Ring-algoritme. Deze algoritmen gaan ervan uit dat elke kandidaat een unieke id heeft en op een stabiele wijze kan communiceren met de andere kandidaten.

Problemen en overwegingen

Beschouw de volgende punten als u besluit hoe u dit patroon wilt implementeren:

  • Het proces van het selecteren van een leider moet overweg kunnen met tijdelijke en permanente storingen.
  • Het moet mogelijk zijn om te detecteren wanneer de leider is uitgevallen of anderszins niet meer beschikbaar is (zoals vanwege een communicatiestoring). Hoe snel deze detectie moet zijn, verschilt per systeem. Het is mogelijk dat sommige systemen korte tijd zonder een leider kunnen werken, zodat in die periode een eventuele tijdelijke fout kan worden opgelost. In andere gevallen kan het nodig zijn dat de uitval van de leider direct wordt gedetecteerd en dat er meteen een nieuwe leider wordt geselecteerd.
  • In een systeem met ondersteuning voor automatisch horizontaal schalen, kan de leider wegvallen als het systeem terugschaalt en enkele rekenbronnen worden afgesloten.
  • De keuze voor een gedeelde, gedistribueerde mutex betekent dat er sprake is van een afhankelijkheid van de externe service die de mutex levert. De service vormt een Single Point of Failure. Als de service om wat voor reden dan ook niet beschikbaar is, kan er geen leider worden geselecteerd.
  • De keuze van een speciaal toegewezen proces als de leider is een ongecompliceerde benadering. Als het proces echter uitvalt, kan er een aanzienlijke vertraging optreden terwijl het proces opnieuw wordt opgestart. De resulterende latentie kan gevolgen hebben voor de prestaties en reactietijden van andere processen als deze wachten op het coördineren van een bewerking door de leider.
  • Het handmatig implementeren van een van de algoritmen voor het selecteren van een leider biedt de grootste flexibiliteit bij het afstemmen en optimaliseren van de code.
  • Voorkom dat de leider een knelpunt wordt in het systeem. Het doel van de leider is het coördineren van het werk van de onderliggende taken en het hoeft niet noodzakelijkerwijs deel te nemen aan dit werk zelf, hoewel dit wel moet kunnen als de taak niet als leider wordt gekozen.

Wanneer dit patroon gebruiken

Gebruik dit patroon wanneer de taken in een gedistribueerde toepassing, zoals een in de cloud gehoste oplossing, zorgvuldige coördinatie vereisen en er geen natuurlijke leider is.

In de volgende gevallen is dit patroon mogelijk niet geschikt:

  • Er is een natuurlijke leider of een toegewezen proces dat altijd als de leider kan fungeren. Zo is het bijvoorbeeld mogelijk om een singleton-proces te implementeren dat de taakexemplaren coördineert. Als dit proces uitvalt of slecht functioneert, kan het systeem het proces afsluiten en opnieuw starten.
  • De coördinatie tussen taken kan worden bereikt via een minder ingrijpende methode. Als verschillende taakexemplaren bijvoorbeeld alleen maar gecoördineerde toegang tot een gedeelde resource nodig hebben, kunt u beter kiezen voor optimistische of pessimistische vergrendeling om de toegang te regelen.
  • Een oplossing van derden is meer geschikt. De Microsoft Azure HDInsight-service (op basis van Apache Hadoop) gebruikt bijvoorbeeld de services van Apache Zookeeper om de toewijzing te coördineren en het aantal taken te verkleinen die gegevens verzamelen en samenvatten.

Workloadontwerp

Een architect moet evalueren hoe het Leader Election-patroon kan worden gebruikt in het ontwerp van hun workload om de doelstellingen en principes te verhelpen die worden behandeld in de pijlers van het Azure Well-Architected Framework. Voorbeeld:

Pijler Hoe dit patroon ondersteuning biedt voor pijlerdoelen
Beslissingen over betrouwbaarheidsontwerp helpen uw workload bestand te worden tegen storingen en ervoor te zorgen dat deze herstelt naar een volledig functionerende status nadat er een fout is opgetreden. Dit patroon vermindert het effect van storingen in knooppunten door werk betrouwbaar om te leiden. Het implementeert ook failover via consensusalgoritmen wanneer een leider defect is.

- RE:05 Redundantie
- RE:07 Zelfherstel

Net als bij elke ontwerpbeslissing moet u rekening houden met eventuele compromissen ten opzichte van de doelstellingen van de andere pijlers die met dit patroon kunnen worden geïntroduceerd.

Opmerking

Het Leader Election-voorbeeld op GitHub) laat zien hoe u een lease op een Azure Storage-blob gebruikt om een mechanisme te bieden voor het implementeren van een gedeelde, gedistribueerde mutex. Deze mutex kan worden gebruikt om een leider te kiezen tussen een groep beschikbare werkrolexemplaren. De eerste instantie voor het verkrijgen van de lease wordt gekozen als leider en blijft de leider totdat de lease wordt vrijgegeven of de lease niet kan worden verlengd. Andere werkrolexemplaren kunnen de blob-lease blijven bewaken als de leider niet meer beschikbaar is.

Een blob-lease is een exclusieve schrijfvergrendeling van een blob. Een blob kan altijd maar door één lease worden geclaimd. Een werkrolexemplaren kunnen een lease aanvragen via een opgegeven blob en de lease wordt verleend als er geen ander werkrolexemplaren een lease over dezelfde blob bevatten. Anders genereert de aanvraag een uitzondering.

Als u wilt voorkomen dat een instantie van een leider de lease voor onbepaalde tijd behoudt, geeft u een levensduur voor de lease op. Als deze is verlopen, komt de lease weer beschikbaar. Hoewel een instantie de lease behoudt, kan deze echter aanvragen dat de lease wordt verlengd en wordt de lease gedurende een langere periode verleend. Het exemplaar van de leider kan dit proces voortdurend herhalen als deze de lease wil behouden. Zie Lease Blob (REST-API) voor meer informatie over de lease van een blob.

De BlobDistributedMutex klasse in het onderstaande C#-voorbeeld bevat de RunTaskWhenMutexAcquired methode waarmee een werkrolexemplaren een lease kunnen verkrijgen via een opgegeven blob. De details van de blob (de naam, de container en het opslagaccount) worden doorgegeven aan de constructor in een BlobSettings-object op het moment dat het BlobDistributedMutex-object wordt gemaakt (dit object is een eenvoudige struct die is opgenomen in de voorbeeldcode). De constructor accepteert ook een Task code die verwijst naar de code die het werkrolexemplaren moeten uitvoeren als deze de lease over de blob heeft verkregen en de leider wordt gekozen. De code die verantwoordelijk is voor het afhandelen van de details van het verkrijgen van de lease is geïmplementeerd in een afzonderlijke helperklasse met de naam 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);
  }
  ...

De methode RunTaskWhenMutexAcquired in het bovenstaande codevoorbeeld roept de methode RunTaskWhenBlobLeaseAcquired aan (zie het volgende codevoorbeeld) om daadwerkelijk in bezit te komen van de lease. De methode RunTaskWhenBlobLeaseAcquired wordt asynchroon uitgevoerd. Als de lease is verkregen, is het werkrolexemplaren geselecteerd als leider. Het doel van de taskToRunWhenLeaseAcquired gemachtigde is het uitvoeren van het werk dat de andere werkrolexemplaren coördineert. Als de lease niet wordt verkregen, is een ander werkrolexemplaren geselecteerd als de leider en blijft het huidige werkrolexemplaren een ondergeschikte. De methode TryAcquireLeaseOrWait is een helpermethode die het BlobLeaseManager-object gebruikt voor het verkrijgen van de lease.

  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);
          ...
        }
      }
    }
    ...
  }

De taak die wordt gestart door de leider wordt eveneens asynchroon uitgevoerd. Terwijl deze taak wordt uitgevoerd, probeert de methode RunTaskWhenBlobLeaseAcquired uit het volgende codevoorbeeld de lease periodiek te vernieuwen. Dit helpt ervoor te zorgen dat het werkrolexemplaren de leider blijven. In de voorbeeldoplossing is de vertraging tussen verlengingsaanvragen minder dan de tijd die is opgegeven voor de duur van de lease om te voorkomen dat een ander werkrolexemplaren worden gekozen als leider. Als de verlenging om welke reden dan ook mislukt, wordt de opvultaak geannuleerd.

Als de lease niet kan worden vernieuwd of de taak wordt geannuleerd (mogelijk als gevolg van het afsluiten van het werkrolexemplaren), wordt de lease vrijgegeven. Op dit moment kan dit of een ander werkrolexemplaren worden gekozen als de leider. Het codefragment hieronder illustreert een deel van het proces.

  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);
        }
      }
    }
  }
  ...
}

De methode KeepRenewingLease is een andere helpermethode die het BlobLeaseManager-object gebruikt voor het vernieuwen van de lease. De methode CancelAllWhenAnyCompletes annuleert de taken die zijn opgegeven als de eerste twee parameters. Het volgende diagram laat zien hoe u met behulp van de klasse BlobDistributedMutex een leider selecteert en een taak uitvoert die bewerkingen coördineert.

Afbeelding 1 illustreert de functies van de klasse BlobDistributedMutex

In het volgende codevoorbeeld ziet u hoe u de BlobDistributedMutex klasse binnen een werkrolexemplaren gebruikt. Deze code verkrijgt een lease via een blob met de naam MyLeaderCoordinatorTask in de container Azure Blob Storage van de lease en geeft aan dat de code die in de MyLeaderCoordinatorTask methode is gedefinieerd, moet worden uitgevoerd als het werkrolexemplaren de leider wordt gekozen.

// 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)
{
  ...
}

Enkele belangrijke aandachtspunten voor de voorbeeldoplossing:

  • De blob is een potentieel Single Point of Failure. Als de blobservice niet meer beschikbaar is of niet toegankelijk is, kan de leider de lease niet verlengen en kan geen ander werkrolexemplaren de lease verkrijgen. In dit geval kan geen werkrolinstantie fungeren als de leider. De blob-service is echter ontworpen voor tolerantie, zodat een volledige uitval van de blob-service zeer onwaarschijnlijk is.
  • Als de taak die door de leider wordt uitgevoerd, blijft de leider de lease verlengen, waardoor andere werkrolinstanties de lease niet kunnen verkrijgen en de positie van de leider overnemen om taken te coördineren. In de praktijk moet de status van de leider dan ook regelmatig worden gecontroleerd.
  • Het selectieproces is niet-deterministisch. U kunt geen veronderstellingen maken over welk werkrolexemplaren de blob-lease verkrijgen en de leider worden.
  • De blob die wordt gebruikt als het doel van de blob-lease mag niet voor andere doeleinden worden gebruikt. Als een werkrolinstantie probeert gegevens op te slaan in deze blob, zijn deze gegevens niet toegankelijk, tenzij het werkrolexemplaren de leider is en de blob-lease bevat.

Volgende stappen

De volgende richtlijnen zijn mogelijk ook relevant bij de implementatie van dit patroon: