Model volby vedoucího procesu
Koordinuje akce prováděné kolekcí spolupracujících instancí v distribuované aplikaci. Zvolí jednu instanci jako vedoucí a ta potom přijme zodpovědnost za správu ostatních instancí. To vám pomůže předejít tomu, aby docházelo ke vzájemným konfliktům instancí, kolizím ve využívání sdílených prostředků nebo situacím, kdy jedna instance nedopatřením zasahuje do činnosti jiné instance.
Kontext a problém
Typická cloudová aplikace provádí koordinovaně celou řadu úloh. Všechny tyto úlohy můžou být instance, které spouštějí stejný kód a vyžadují přístup ke stejným prostředkům, nebo můžou běžet paralelně a provádět dílčí úkoly nějakého složitějšího výpočtu.
Instance úloh můžou dlouho běžet samostatně, může však být také potřeba akce jednotlivých instancí koordinovat, aby nedocházelo ke vzájemným konfliktům, kolizím ve využívání sdílených prostředků nebo situacím, kdy jedna instance nedopatřením zasahuje do činnosti jiné instance.
Například:
- V cloudovém systému, který implementuje horizontální škálování, může běžet současně více instancí stejné úlohy, kdy každá instance obsluhuje jiného uživatele. Pokud tyto instance zapisují do sdíleného prostředku, je potřeba koordinovat jejich akce, aby nedocházelo k tomu, že jedna instance bude přepisovat změny provedené jinou instancí.
- Pokud úlohy běží paralelně a každá provádí určitou část jednoho komplexního výpočtu, je potřeba po dokončení činnosti všech instancí agregovat výsledky.
Všechny instance úlohy jsou rovnocenné a žádná z nich neplní roli vedoucí instance, která by činnost všech ostatních koordinovala nebo provedla agregaci výsledků.
Řešení
Je potřeba určit jednu instanci úlohy jako vedoucí instanci, která bude koordinovat akce ostatních podřízených instancí. Pokud všechny instance úlohy spouštějí stejný kód, může vedoucí roli plnit kterákoli z nich. Proces zvolení vedoucí instance je potřeba nastavit důkladně tak, aby nemohla nastat situace, kdy se vedoucí úlohy zhostí dvě nebo více instancí zároveň.
Mechanismus zvolení vedoucí instance musí být robustní. Musí počítat s událostmi, jako je výpadek sítě nebo selhání procesu. V mnoha řešeních monitorují podřízené instance vedoucí instanci prostřednictvím prezenčního signálu nebo cyklického dotazování. Pokud činnost vedoucí instance úlohy neočekávaně skončí nebo je kvůli selhání sítě pro podřízené instance nedostupná, je nutné, aby tyto instance zvolily novou vedoucí instanci.
Existuje několik strategií, jak zvolit vedoucí instanci ze skupiny úloh v distribuovaném prostředí:
- Výběr instance úlohy s nejnižším ID instance nebo procesu.
- Získání sdíleného, distribuovaného vzájemně vyloučeného přístupu. První instance úlohy, která získá vzájemně vyloučený přístup, se stává vedoucí instancí. Systém však musí zajistit, aby se v případě, že vedoucí instance skončí nebo se odpojí od zbytku systému, vytvořil nový vzájemně vyloučený přístup, který umožní, že se vedoucí instancí může stát nová instance úlohy.
- Implementace některého z běžných algoritmů volby vedoucího procesu, jako je bully algoritmus (algoritmus výběru vhazováním) nebo ring algoritmus (volba směrovaná po komunikačním kruhu). Tyto algoritmy předpokládají, že všichni kandidáti účastnící se volby mají jedinečné ID a že můžou spolehlivě komunikovat s ostatními kandidáty.
Problémy a důležité informace
Když se budete rozhodovat, jak tento model implementovat, měli byste vzít v úvahu následující skutečnosti:
- Proces volby vedoucí instance by měl být odolný vůči přechodným i trvalým chybám.
- Je potřeba implementovat proces, který zjistí, že vedoucí instance přestala fungovat nebo je z jiného důvodu nedostupná (například kvůli selhání komunikace). Jak rychle je potřeba nefunkčnost vedoucí instance zjistit, závisí na systému. Některé systémy dokážou fungovat krátce i bez vedoucího procesu a když se objeví přechodná chyba, může být opravena dřív, než to fungování systému nějak naruší. V jiných případech může být potřeba, aby bylo selhání vedoucí instance zjištěno okamžitě a aby ihned proběhla nová volba.
- V systému, který implementuje automatické horizontální škálování, může být vedoucí proces ukončen ve chvíli, kdy se obnoví původní škálování a ukončí se některé výpočetní prostředky.
- Při použití sdíleného, distribuovaného vzájemně vyloučeného přístupu se v externí službě, která vzájemně vyloučený přístup poskytuje, vytvoří závislost. Služba představuje kritický prvek způsobující selhání. Když bude z nějakého důvodu nedostupná, systém nebude moct zvolit vedoucí instanci.
- Jednoduchým řešením by bylo použít jeden vyhrazený vedoucí proces. Pokud ale takový proces selže, může dojít k poměrně dlouhodobému výpadku, než se opět restartuje. Způsobená latence může ovlivnit výkon a dobu odezvy dalších procesů, které můžou čekat, až vedoucí proces zkoordinuje určitou operaci.
- Nejvyšší flexibilitu pro ladění a optimalizaci kódu poskytuje ruční implementace jednoho z algoritmů volby vedoucího procesu.
Kdy se má tento model použít
Tento model použijte, pokud úlohy v distribuované aplikaci, například v řešeních hostovaných v cloudu, vyžadují pečlivou koordinaci a žádný proces není přirozeně vedoucí.
Předejděte tomu, aby se vedoucí proces stal kritickým bodem v systému. Účelem vedoucího procesu je koordinovat činnost podřízených úloh – sám se této činnosti účastnit nemusí, ovšem pro případ, že vedoucím procesem zvolen nebude, by měl být schopný ji provádět.
Tento model nebude pravděpodobně vhodný v následujících případech:
- Existuje přirozený nebo určený vedoucí proces, který může roli vedoucího procesu plnit vždycky. Například je možné implementovat jediný proces, který bude koordinovat instance úlohy. Pokud tento proces selže nebo je poškozen, systém ho může vypnout a restartovat.
- Koordinace úloh lze dosáhnout jednodušším způsobem. Například pokud je pouze potřeba koordinovat přístup několika instancí úlohy ke sdíleným prostředkům, je lepším řešením řízení přístupu pomocí optimistického nebo pesimistického zamykání.
- Řešení třetí strany je vhodnější. Například služba Microsoft Azure HDInsight (založená na Apache Hadoopu) používá služby od Apache Zookeeperu ke koordinaci mapy a omezení úloh shromažďujících a shrnujících data.
Příklad
Projekt DistributedMutex v řešení LeaderElection (ukázku tohoto modelu najdete na GitHubu) demonstruje, jak zavést mechanismus implementace sdíleného, distribuovaného vzájemně vyloučeného přístupu použitím zapůjčení v objektu blob služby Azure Storage. Tento vzájemně vyloučený přístup lze použít k volbě vedoucího procesu ve skupině instancí role v cloudové službě Azure. První instance role, která získá zapůjčení, je zvolena vedoucí instancí a tou zůstane, dokud zapůjčení neuvolní nebo dokud je schopná zapůjčení obnovovat. Další instance role můžou zapůjčení objektu blob monitorovat pro případ, že vedoucí instance přestane být dostupná.
Zapůjčení objektu blob funguje u objektu blob jako výhradní zámek proti zápisu. Jeden objekt blob může být zapůjčen vždy jen jedné instanci. Instance role může požádat o zapůjčení konkrétního objektu blob a toto zapůjčení budete schváleno, pokud už tento objekt blob nemá zapůjčený jiná instance role. V opačném případě vyvolá žádost výjimku.
Abyste předešli tomu, že si chybná instance role ponechá zapůjčení na neomezenou dobu, určete pro zapůjčení maximální dobu platnosti. Když tato platnost vyprší, bude zapůjčení opět k dispozici. Instance role, která získala zapůjčení, však může požádat o jeho obnovení a zapůjčení bude povoleno na delší časové období. Pokud si chce instance role zapůjčení ponechat, může tento proces neustále opakovat. Další informace o způsobech zapůjčení objektu blob najdete v tématu Zapůjčení objektu blob (REST API).
Třída BlobDistributedMutex v následujícím příkladu kódu C# obsahuje metodu RunTaskWhenMutexAcquired, pomocí které se může instance role pokoušet o zapůjčení konkrétního objektu blob. Podrobnosti o objektu blob (název, kontejner a účet úložiště) se předávají konstruktoru v objektu BlobSettings při vytvoření objektu BlobDistributedMutex (tento objekt je jednoduchá struktura, která je součástí ukázkového kódu). Konstruktor také přijímá objekt Task odkazující na kód, který by měla instance role spustit, když úspěšně získá zapůjčení objektu blob a stane se vedoucí instancí. Všimněte si, že kód, který zpracovává podrobnosti získání zapůjčení na nižší úrovni, je implementovaný v samostatné pomocné třídě 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 v ukázkovém kódu výše vyvolá metodu RunTaskWhenBlobLeaseAcquired použitou v následujícím ukázkovém kódu, která provede samotné získání zapůjčení. Metoda RunTaskWhenBlobLeaseAcquired běží asynchronně. Pokud je zapůjčení úspěšné, instance role je zvolena vedoucí instancí. Účelem delegáta taskToRunWhenLeaseAcquired je provádět koordinaci ostatních instancí role. Pokud zapůjčení není úspěšné, je vedoucí instancí zvolena jiná instance role a aktuální instance role zůstává této instanci podřízená. TryAcquireLeaseOrWait je pomocná metoda, která prostřednictvím objektu BlobLeaseManager získává zapůjčení.
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);
...
}
}
}
...
}
Také úloha spuštěná vedoucí instancí běží asynchronně. Zatímco tato úloha běží, metoda RunTaskWhenBlobLeaseAcquired použitá v následujícím ukázkovém kódu se pravidelně pokouší zapůjčení obnovit. To pomáhá zajistit, že tato instance role zůstane vedoucí instancí. Prodleva mezi jednotlivými žádostmi o obnovení v ukázkovém řešení je kratší než nastavená doba trvání zapůjčení – tím se předejde tomu, aby byla vedoucí instancí zvolena jiná instance role. Pokud obnovení z nějakého důvodu selže, úloha se zruší.
Pokud selže obnovení zapůjčení nebo dojde ke zrušení úlohy (pravděpodobně v důsledku ukončení instance role), zapůjčení se uvolní. Tehdy může být vedoucí instancí zvolena znovu stejná nebo jiná instance role. Úryvek kódu níže demonstruje tuto část 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);
}
}
}
}
...
}
KeepRenewingLease je další pomocná metoda, která prostřednictvím objektu BlobLeaseManager obnovuje zapůjčení. Metoda CancelAllWhenAnyCompletes zruší úlohy určené prvními dvěma parametry. Následující diagram znázorňuje použití třídy BlobDistributedMutex ke zvolení vedoucí instance a spuštění úlohy, která koordinuje operace.

Následující příklad kódu ukazuje, jak používat třídu BlobDistributedMutex v roli pracovního procesu. Tento kód získává zapůjčení objektu blob s názvem MyLeaderCoordinatorTask v kontejneru zapůjčení v úložišti pro vývoj a určuje, že v případě zvolení instance role vedoucí instancí se má spustit kód definovaný v metodě MyLeaderCoordinatorTask.
var settings = new BlobSettings(CloudStorageAccount.DevelopmentStorageAccount,
"leases", "MyLeaderCoordinatorTask");
var cts = new CancellationTokenSource();
var mutex = new BlobDistributedMutex(settings, MyLeaderCoordinatorTask);
mutex.RunTaskWhenMutexAcquired(this.cts.Token);
...
// Method that runs if the role instance is elected the leader
private static async Task MyLeaderCoordinatorTask(CancellationToken token)
{
...
}
Několik poznámek k ukázkovému řešení:
- Objekt blob je potenciální kritický prvek způsobující selhání. Pokud služba objektu blob není z nějakého důvodu dostupná, nebude moct vedoucí instance obnovit zapůjčení, které nebude moct získat ani žádná jiná instance role. V takovémto případě nebude moct žádná instance role plnit roli vedoucí instance. Služba objektů blob je však navržená jako odolné řešení, takže její úplné selhání se považuje za extrémně nepravděpodobné.
- Pokud se úloha prováděná vedoucí instancí zablokuje, může vedoucí instance nadále obnovovat zapůjčení, a tím bránit všem ostatním instancím role v získání zapůjčení a převzetí vedení za účelem koordinace úloh. V ostrém provozu je potřeba stav vedoucí instance pravidelně kontrolovat.
- Proces volby je nedeterministický. Nelze nijak odhadnout, která instance role získá zapůjčení objektu blob a stane se vedoucí instancí.
- Objekt blob používaný jako cíl zapůjčení objektu blob není vhodné používat k žádnému jinému účelu. Pokud se instance role pokusí v tomto objektu blob uložit data, nebudou tato data přístupná, pokud tato instance role není vedoucí instancí a nemá objekt blob zapůjčený.
Další kroky
Při implementaci tohoto modelu můžou být relevantní také následující pokyny:
- Součástí tohoto modelu je ukázková aplikace ke stažení.
- Pokyny pro automatické škálování. Je možné spouštět a ukončovat instance hostitelů úlohy v závislosti na tom, jak se mění zatížení aplikace. Automatické škálování může usnadnit udržování propustnosti a výkonu zpracování v obdobích špičky.
- Pokyny k dělení výpočetních prostředků. Tento návod popisuje, jak přidělovat úlohy hostitelům v cloudové službě způsobem, který pomůže minimalizovat provozní náklady při zachování škálovatelnosti, výkonu, dostupnosti a zabezpečení služby.
- Asynchronní vzor založený na úlohě.
- Příklad ilustrující bully algoritmus.
- Příklad ilustrující ring algoritmus.
- Apache Curator – klientská knihovna pro Apache ZooKeeper.
- Článek Zapůjčení objektu blob (REST API) na webu MSDN.