Wzorzec wyborów lidera
Koordynowanie akcji wykonywanych przez kolekcję współpracujących wystąpień w aplikacji rozproszonej przez wybranie jednego wystąpienia jako lidera, które przyjmuje odpowiedzialność za zarządzanie innymi wystąpieniami. Może to pomóc w zapewnieniu, że wystąpienia nie powodują konfliktu ze sobą, powodują rywalizację o udostępnione zasoby lub przypadkowo zakłócają pracę, którą wykonują inne wystąpienia.
Kontekst i problem
Typowa aplikacja w chmurze ma wiele zadań działających w skoordynowany sposób. Wszystkie te zadania mogą być wystąpieniami, w których działa ten sam kod i wymagają dostępu do tych samych zasobów, lub mogą one działać równolegle w celu wykonania poszczególnych części złożonego obliczenia.
Wystąpienia zadań mogą działać oddzielnie przez większą część czasu, ale może być również konieczne koordynowanie akcji każdego wystąpienia w celu zapewnienia, że nie powodują konfliktów, powodują rywalizację o udostępnione zasoby lub przypadkowo zakłócają pracę wykonywanej przez inne wystąpienia zadań.
Przykład:
- W systemie opartym na chmurze, który implementuje skalowanie w poziomie, wiele wystąpień tego samego zadania może być uruchomionych jednocześnie z każdym wystąpieniem obsługującym innego użytkownika. Jeśli te wystąpienia zapisują w zasobie udostępnionym, konieczne jest koordynowanie ich akcji, aby zapobiec zastąpieniu zmian wprowadzonych przez inne wystąpienia.
- Jeśli zadania wykonują poszczególne elementy złożonego obliczenia równolegle, wyniki muszą zostać zagregowane po zakończeniu wszystkich zadań.
Wystąpienia zadań są wszystkimi elementami równorzędnymi, więc nie ma naturalnego lidera, który może pełnić rolę koordynatora lub agregatora.
Rozwiązanie
Jedno wystąpienie zadania powinno zostać wybrane do działania jako lider, a to wystąpienie powinno koordynować działania innych podrzędnych wystąpień zadań. Jeśli wszystkie wystąpienia zadań uruchamiają ten sam kod, każdy z nich może działać jako lider. W związku z tym proces wyborów musi być starannie zarządzany, aby zapobiec jednoczesnemu przejęciu co najmniej dwóch wystąpień na stanowisku lidera.
System musi zapewnić niezawodny mechanizm wybierania lidera. Ta metoda musi poradzić sobie z zdarzeniami, takimi jak awarie sieci lub błędy procesów. W wielu rozwiązaniach podrzędne wystąpienia zadań monitorują lidera za pomocą jakiejś metody pulsu lub sondowania. Jeśli wyznaczony lider nieoczekiwanie zakończy działanie lub awaria sieci sprawia, że lider jest niedostępny dla wystąpień podrzędnych zadań, konieczne jest wybranie nowego lidera.
Istnieje wiele strategii wybierania lidera między zestawem zadań w środowisku rozproszonym, w tym:
- Wyścigi w celu uzyskania udostępnionego, rozproszonego mutexu. Pierwsze wystąpienie zadania, które uzyskuje mutex, jest liderem. Jednak system musi upewnić się, że jeśli lider zakończy działanie lub zostanie odłączony od reszty systemu, mutex zostanie zwolniony, aby umożliwić innemu wystąpieniu zadania stanie się liderem. Ta strategia została pokazana w poniższym przykładzie.
- Implementowanie jednego z typowych algorytmów wyboru lidera, takich jak algorytm bully, algorytm konsensusu raft lub algorytm pierścieniowy. Algorytmy te zakładają, że każdy kandydat w wyborach ma unikatowy identyfikator i że może niezawodnie komunikować się z innymi kandydatami.
Problemy i zagadnienia
Podczas podejmowania decyzji o sposobie implementacji tego wzorca należy wziąć pod uwagę następujące kwestie:
- Proces wybierania lidera powinien być odporny na przejściowe i trwałe awarie.
- Należy wykryć, kiedy lider uległ awarii lub stał się niedostępny (na przykład z powodu awarii komunikacji). Szybkość wykrywania jest zależna od systemu. Niektóre systemy mogą działać przez krótki czas bez lidera, podczas którego może zostać naprawiona usterka przejściowa. W innych przypadkach może być konieczne natychmiastowe wykrycie niepowodzenia lidera i wyzwolenie nowych wyborów.
- W systemie, który implementuje skalowanie automatyczne w poziomie, lider może zostać zakończony, jeśli system skaluje się z powrotem i zamyka niektóre zasoby obliczeniowe.
- Użycie udostępnionego, rozproszonego mutexu wprowadza zależność od usługi zewnętrznej, która zapewnia mutex. Usługa stanowi pojedynczy punkt awarii. Jeśli stanie się niedostępna z jakiegokolwiek powodu, system nie będzie mógł wybrać lidera.
- Użycie jednego dedykowanego procesu jako lidera jest prostym podejściem. Jeśli jednak proces zakończy się niepowodzeniem, może wystąpić znaczne opóźnienie podczas jego ponownego uruchamiania. Wynikowe opóźnienie może mieć wpływ na wydajność i czasy odpowiedzi innych procesów, jeśli oczekują, aż lider będzie koordynować operację.
- Ręczne implementowanie jednego z algorytmów wyboru lidera zapewnia największą elastyczność dostrajania i optymalizowania kodu.
- Unikaj tworzenia wąskiego gardła w systemie. Celem lidera jest koordynowanie pracy podrzędnych zadań i nie musi uczestniczyć w tej pracy — chociaż powinno być w stanie to zrobić, jeśli zadanie nie zostanie wybrane jako lider.
Kiedy należy używać tego wzorca
Użyj tego wzorca, gdy zadania w aplikacji rozproszonej, takie jak rozwiązanie hostowane w chmurze, wymagają starannej koordynacji i nie ma naturalnego lidera.
Ten wzorzec może nie być przydatny, jeśli:
- Istnieje naturalny lider lub dedykowany proces, który zawsze może działać jako lider. Na przykład może być możliwe zaimplementowanie pojedynczego procesu, który koordynuje wystąpienia zadań. Jeśli ten proces ulegnie awarii lub stanie się w złej kondycji, system może go zamknąć i uruchomić ponownie.
- Koordynację między zadaniami można osiągnąć przy użyciu bardziej uproszczonej metody. Jeśli na przykład kilka wystąpień zadań po prostu potrzebuje skoordynowanego dostępu do udostępnionego zasobu, lepszym rozwiązaniem jest użycie optymistycznego lub pesymistycznego blokowania w celu kontrolowania dostępu.
- Rozwiązanie innych firm, takie jak Apache Zookeeper , może być bardziej wydajnym rozwiązaniem.
Projekt obciążenia
Architekt powinien ocenić, w jaki sposób wzorzec wyboru lidera może być używany w projekcie obciążenia, aby sprostać celom i zasadom opisanym w filarach platformy Azure Well-Architected Framework. Przykład:
Filar | Jak ten wzorzec obsługuje cele filaru |
---|---|
Decyzje projektowe dotyczące niezawodności pomagają obciążeniu stać się odporne na awarię i zapewnić, że zostanie przywrócony do w pełni funkcjonalnego stanu po wystąpieniu awarii. | Ten wzorzec zmniejsza wpływ awarii węzła przez niezawodne przekierowywanie pracy. Implementuje również tryb failover za pośrednictwem algorytmów konsensusu w przypadku awarii lidera. - Nadmiarowość RE:05 - RE:07 Samonaprawiania |
Podobnie jak w przypadku każdej decyzji projektowej, należy rozważyć wszelkie kompromisy w stosunku do celów innych filarów, które mogą zostać wprowadzone przy użyciu tego wzorca.
Przykład
Przykład Wyboru lidera w witrynie GitHub pokazuje, jak używać dzierżawy w obiekcie blob usługi Azure Storage w celu zapewnienia mechanizmu implementowania udostępnionego, rozproszonego mutexu. Ten mutex może służyć do wybierania lidera wśród grupy dostępnych wystąpień procesów roboczych. Pierwsze wystąpienie do uzyskania dzierżawy jest wybierane jako lider i pozostaje liderem, dopóki dzierżawa nie zostanie zwolniona lub nie będzie mogła odnowić dzierżawy. Inne wystąpienia procesów roboczych mogą nadal monitorować dzierżawę obiektu blob, jeśli lider nie jest już dostępny.
Dzierżawa obiektu blob to wyłączna blokada zapisu w obiekcie blob. Pojedynczy obiekt blob może być przedmiotem tylko jednej dzierżawy w dowolnym momencie. Wystąpienie procesu roboczego może zażądać dzierżawy określonego obiektu blob i zostanie przyznane dzierżawie, jeśli żadne inne wystąpienie procesu roboczego nie posiada dzierżawy tego samego obiektu blob. W przeciwnym razie żądanie zgłosi wyjątek.
Aby uniknąć błędnego wystąpienia lidera, które zachowuje dzierżawę na czas nieokreślony, określ okres istnienia dzierżawy. Po wygaśnięciu dzierżawa stanie się dostępna. Jednak gdy wystąpienie przechowuje dzierżawę, może zażądać odnowienia dzierżawy, a dzierżawa zostanie udzielona przez kolejny okres. Wystąpienie lidera może stale powtarzać ten proces, jeśli chce zachować dzierżawę. Aby uzyskać więcej informacji na temat dzierżawy obiektu blob, zobacz Dzierżawa obiektu blob (interfejs API REST).
Klasa BlobDistributedMutex
w poniższym przykładzie w języku C# zawiera RunTaskWhenMutexAcquired
metodę, która umożliwia wystąpieniu procesu roboczego podjęcie próby uzyskania dzierżawy dla określonego obiektu blob. Szczegóły obiektu blob (nazwa, kontener i konto magazynu) są przekazywane do konstruktora w BlobSettings
obiekcie podczas BlobDistributedMutex
tworzenia obiektu (ten obiekt jest prostą strukturą zawartą w przykładowym kodzie). Konstruktor akceptuje również element Task
, który odwołuje się do kodu, który powinien zostać uruchomiony przez wystąpienie procesu roboczego, jeśli pomyślnie uzyska dzierżawę obiektu blob i zostanie wybrany liderem. Należy pamiętać, że kod obsługujący szczegóły niskiego poziomu uzyskiwania dzierżawy jest implementowany w oddzielnej klasie pomocniczej o nazwie 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);
}
...
Metoda RunTaskWhenMutexAcquired
w powyższym przykładzie kodu wywołuje RunTaskWhenBlobLeaseAcquired
metodę pokazaną w poniższym przykładzie kodu, aby faktycznie uzyskać dzierżawę. Metoda RunTaskWhenBlobLeaseAcquired
jest uruchamiana asynchronicznie. Jeśli dzierżawa zostanie pomyślnie przejęta, wystąpienie procesu roboczego zostanie wybrane jako lider. Celem delegata taskToRunWhenLeaseAcquired
jest wykonanie pracy, która koordynuje inne wystąpienia procesów roboczych. Jeśli dzierżawa nie zostanie przejęta, inne wystąpienie procesu roboczego zostało wybrane jako lider, a bieżące wystąpienie procesu roboczego pozostaje podrzędne. Należy pamiętać, że TryAcquireLeaseOrWait
metoda jest metodą pomocnika, która używa BlobLeaseManager
obiektu do uzyskania dzierżawy.
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);
...
}
}
}
...
}
Zadanie uruchomione przez lidera jest również uruchamiane asynchronicznie. Gdy to zadanie jest uruchomione, RunTaskWhenBlobLeaseAcquired
metoda pokazana w poniższym przykładzie kodu okresowo próbuje odnowić dzierżawę. Pomaga to zapewnić, że wystąpienie procesu roboczego pozostanie liderem. W przykładowym rozwiązaniu opóźnienie między żądaniami odnowienia jest mniejsze niż czas określony przez czas trwania dzierżawy, aby zapobiec wybraniu innego wystąpienia procesu roboczego jako lidera. Jeśli odnowienie zakończy się niepowodzeniem z jakiegokolwiek powodu, zadanie specyficzne dla lidera zostanie anulowane.
Jeśli dzierżawa nie zostanie odnowiona lub zadanie zostanie anulowane (prawdopodobnie w wyniku zamknięcia wystąpienia procesu roboczego), dzierżawa zostanie zwolniona. W tym momencie można wybrać to lub inne wystąpienie procesu roboczego jako lider. Poniższy fragment kodu pokazuje tę część procesu.
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);
}
}
}
}
...
}
Metoda KeepRenewingLease
jest inną metodą pomocnika, która używa BlobLeaseManager
obiektu do odnowienia dzierżawy. Metoda CancelAllWhenAnyCompletes
anuluje zadania określone jako dwa pierwsze parametry. Na poniższym diagramie przedstawiono użycie BlobDistributedMutex
klasy do wybierania lidera i uruchamiania zadania, które koordynuje operacje.
W poniższym przykładzie kodu pokazano, jak używać BlobDistributedMutex
klasy w wystąpieniu procesu roboczego. Ten kod uzyskuje dzierżawę obiektu blob o nazwie MyLeaderCoordinatorTask
w kontenerze dzierżawy usługi Azure Blob Storage i określa, że kod zdefiniowany w MyLeaderCoordinatorTask
metodzie powinien zostać uruchomiony, jeśli wystąpienie procesu roboczego zostanie wybrane jako lider.
// 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)
{
...
}
Zwróć uwagę na następujące kwestie dotyczące przykładowego rozwiązania:
- Obiekt blob jest potencjalnym pojedynczym punktem awarii. Jeśli usługa blob stanie się niedostępna lub jest niedostępna, lider nie będzie mógł odnowić dzierżawy, a żadne inne wystąpienie procesu roboczego nie będzie mogło uzyskać dzierżawy. W takim przypadku żadne wystąpienie procesu roboczego nie będzie mogło działać jako lider. Jednak usługa blob została zaprojektowana tak, aby była odporna, dlatego kompletna awaria usługi blob jest uważana za bardzo mało prawdopodobną.
- Jeśli zadanie wykonywane przez lidera zostanie zatrzymane, lider może nadal odnowić dzierżawę, uniemożliwiając innym wystąpieniom procesu roboczego uzyskanie dzierżawy i przejęcie pozycji lidera w celu koordynowania zadań. W świecie rzeczywistym kondycja lidera powinna być sprawdzana w częstych odstępach czasu.
- Proces wyborów jest niedeterministyczny. Nie można podjąć żadnych założeń dotyczących tego, które wystąpienie procesu roboczego uzyska dzierżawę obiektu blob i stanie się liderem.
- Obiekt blob używany jako element docelowy dzierżawy obiektu blob nie powinien być używany w żadnym innym celu. Jeśli wystąpienie procesu roboczego próbuje przechowywać dane w tym obiekcie blob, te dane nie będą dostępne, chyba że wystąpienie procesu roboczego jest liderem i przechowuje dzierżawę obiektu blob.
Dalsze kroki
Następujące wskazówki mogą być również istotne podczas implementowania tego wzorca:
- Ten wzorzec zawiera przykładową aplikację do pobrania.
- Autoscaling Guidance (Wskazówki dotyczące skalowania automatycznego). Istnieje możliwość uruchamiania i zatrzymywania wystąpień hostów zadań w miarę różnicy obciążenia aplikacji. Skalowanie automatyczne może pomóc w utrzymaniu przepływności i wydajności w okresach szczytowego przetwarzania.
- Wzorzec asynchroniczny oparty na zadaniach.
- Przykład ilustrujący algorytm bully.
- Przykład ilustrujący algorytm pierścienia.
- Apache Curator biblioteka klienta dla usługi Apache ZooKeeper.
- Artykuł Dzierżawa obiektu blob (interfejs API REST) w witrynie MSDN.