Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Coordene as ações executadas por uma coleção de instâncias colaboradoras em um aplicativo distribuído, elegendo uma instância como o líder que assume a responsabilidade pelo gerenciamento das outras. Isso pode ajudar a garantir que as instâncias não entrem em conflito umas com as outras, causem contenção por recursos compartilhados ou interfiram inadvertidamente no trabalho que outras instâncias estão executando.
Contexto e problema
Um aplicativo de nuvem típico tem muitas tarefas agindo de forma coordenada. Todas essas tarefas podem ser instâncias executando o mesmo código e exigindo acesso aos mesmos recursos, ou podem estar trabalhando juntas em paralelo para executar as partes individuais de um cálculo complexo.
As instâncias de tarefa podem ser executadas separadamente durante a maior parte do tempo, mas também pode ser necessário coordenar as ações de cada instância para garantir que elas não entrem em conflito, causem contenção de recursos compartilhados ou interfiram acidentalmente no trabalho que outras instâncias de tarefa estão executando.
Por exemplo:
- Em um sistema baseado em nuvem que implementa dimensionamento horizontal, várias instâncias da mesma tarefa podem ser executadas ao mesmo tempo, com cada instância servindo a um usuário diferente. Se essas instâncias gravarem em um recurso compartilhado, será necessário coordenar suas ações para evitar que cada instância substitua as alterações feitas pelas outras.
- Se as tarefas estiverem executando elementos individuais de um cálculo complexo em paralelo, os resultados precisam ser agregados quando todos forem concluídos.
As instâncias de tarefa são todas pares, portanto, não há um líder natural que possa atuar como coordenador ou agregador.
Solução
Uma única instância de tarefa deve ser eleita para atuar como líder, e essa instância deve coordenar as ações das outras instâncias de tarefa subordinadas. Se todas as instâncias de tarefa estiverem executando o mesmo código, cada uma delas será capaz de agir como líder. Por conseguinte, o processo eleitoral deve ser gerido com cuidado para evitar que duas ou mais instâncias assumam a posição de líder ao mesmo tempo.
O sistema deve fornecer um mecanismo robusto para selecionar o líder. Este método tem de lidar com eventos como interrupções de rede ou falhas de processo. Em muitas soluções, as instâncias de tarefas subordinadas monitoram o líder através de algum tipo de método de pulsação ou por sondagem. Se o líder designado terminar inesperadamente ou uma falha de rede tornar o líder indisponível para as instâncias de tarefa subordinadas, é necessário que elejam um novo líder.
Existem várias estratégias para eleger um líder entre um conjunto de tarefas em um ambiente distribuído, incluindo:
- Corrida para adquirir um mutex compartilhado e distribuído. A primeira instância de tarefa que adquire o mutex é o líder. No entanto, o sistema deve garantir que, se o líder terminar ou se desconectar do resto do sistema, o mutex seja liberado para permitir que outra instância de tarefa se torne o líder. Esta estratégia é demonstrada no exemplo seguinte.
- Implementar um dos algoritmos de eleição de líderes comuns, como o Bully Algorithm, o Raft Consensus Algorithm ou o Ring Algorithm. Estes algoritmos pressupõem que cada candidato na eleição tem um ID único e que pode comunicar com os outros candidatos de forma fiável.
Questões e considerações
Considere os seguintes pontos ao decidir como implementar esse padrão:
- O processo de eleição de um líder deve ser resiliente a falhas transitórias e persistentes.
- Deve ser possível detetar quando o líder falhou ou se tornou indisponível (por exemplo, devido a uma falha de comunicação). A rapidez com que a deteção é necessária depende do sistema. Alguns sistemas podem ser capazes de funcionar por um curto período de tempo sem um líder, durante o qual uma falha transitória pode ser corrigida. Em outros casos, pode ser necessário detetar imediatamente o fracasso do líder e desencadear uma nova eleição.
- Em um sistema que implementa dimensionamento automático horizontal, o líder pode ser encerrado se o sistema reduzir e desligar alguns dos recursos de computação.
- O uso de um mutex compartilhado e distribuído introduz uma dependência do serviço externo que fornece o mutex. O serviço constitui um único ponto de falha. Se ficar indisponível por qualquer motivo, o sistema não poderá eleger um líder.
- Usar um único processo dedicado como líder é uma abordagem simples. No entanto, se o processo falhar, pode haver um atraso significativo enquanto ele é reiniciado. A latência resultante pode afetar o desempenho e os tempos de resposta de outros processos se eles estiverem esperando que o líder coordene uma operação.
- A implementação manual de um dos algoritmos de eleição de líderes oferece a maior flexibilidade para ajustar e otimizar o código.
- Evite fazer do líder um gargalo no sistema. O objetivo do líder é coordenar o trabalho das tarefas subordinadas, e ele não precisa necessariamente participar desse trabalho em si – embora deva ser capaz de fazê-lo se a tarefa não for eleita como líder.
Quando usar este padrão
Use esse padrão quando as tarefas em um aplicativo distribuído, como uma solução hospedada na nuvem, precisarem de uma coordenação cuidadosa e não houver um líder natural.
Este padrão pode não ser útil se:
- Há um líder natural ou um processo dedicado que pode sempre agir como o líder. Por exemplo, pode ser possível implementar um processo singleton que coordena as instâncias de tarefa. Se esse processo falhar ou não estiver íntegro, o sistema poderá desligá-lo e reiniciá-lo.
- A coordenação entre tarefas pode ser conseguida utilizando um método mais leve. Por exemplo, se várias instâncias de tarefa simplesmente precisam de acesso coordenado a um recurso compartilhado, uma solução melhor é usar o bloqueio otimista ou pessimista para controlar o acesso.
- Uma solução de terceiros, como o Apache Zookeeper , pode ser uma solução mais eficiente.
Design da carga de trabalho
Um arquiteto deve avaliar como o padrão de Eleição de Líder pode ser usado no design de sua carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. Por exemplo:
| Pilar | Como esse padrão suporta os objetivos do pilar |
|---|---|
| As decisões de projeto de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e a garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. | Esse padrão atenua o efeito de mau funcionamento do nó redirecionando o trabalho de forma confiável. Ele também implementa failover por meio de algoritmos de consenso quando um líder funciona mal. - RE:05 Redundância - RE:07 Auto-cura |
Como em qualquer decisão de design, considere quaisquer compensações em relação aos objetivos dos outros pilares que possam ser introduzidos com esse padrão.
Exemplo
O exemplo de Eleição de Líder no GitHub mostra como usar uma concessão em um blob de Armazenamento do Azure para fornecer um mecanismo para implementar um mutex compartilhado e distribuído. Este mutex pode ser usado para eleger um líder entre um grupo de instâncias de trabalhadores disponíveis. A primeira instância para adquirir o contrato de arrendamento é eleita o líder e permanece como líder até que libere o contrato ou não consiga renovar o contrato. Outras instâncias de trabalho podem continuar monitorando a concessão de blob caso o líder não esteja mais disponível.
Uma locação de blob é um bloqueio de gravação exclusivo sobre um blob. Um único blob pode ser objeto de apenas uma locação a qualquer momento. Uma instância de trabalho pode solicitar uma concessão sobre um blob especificado e será concedida a concessão se nenhuma outra instância de trabalho tiver uma concessão sobre o mesmo blob. Caso contrário, a solicitação lançará uma exceção.
Para evitar que uma instância de líder com defeito mantenha a locação indefinidamente, especifique um tempo de vida para a locação. Quando este expira, o contrato de arrendamento fica disponível. No entanto, enquanto uma instância detém o arrendamento, pode solicitar que o contrato seja renovado, e ser-lhe-á concedido o arrendamento por um período de tempo adicional. A instância líder pode repetir continuamente esse processo se quiser manter a locação. Para obter mais informações sobre como alugar um blob, consulte Lease Blob (REST API).
A BlobDistributedMutex classe no exemplo de C# abaixo contém o RunTaskWhenMutexAcquired método que permite que uma instância de trabalho tente adquirir uma concessão sobre um blob especificado. Os detalhes do blob (o nome, o contêiner e a conta de armazenamento) são passados para o construtor em um BlobSettings objeto quando o BlobDistributedMutex objeto é criado (este objeto é uma estrutura simples incluída no código de exemplo). O construtor também aceita um Task que faz referência ao código que a instância de trabalho deve executar se ele adquirir com êxito a concessão sobre o blob e for eleito o líder. O código que trata dos detalhes de baixo nível da aquisição do arrendamento está implementado numa classe auxiliar separada chamada 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);
}
...
O RunTaskWhenMutexAcquired método no exemplo de código anterior invoca o RunTaskWhenBlobLeaseAcquired método mostrado no exemplo de código seguinte para realmente adquirir o arrendamento. O RunTaskWhenBlobLeaseAcquired método é executado de forma assíncrona. Se o contrato de arrendamento for adquirido com sucesso, a instância do trabalhador foi eleita o líder. O objetivo do delegado é executar o trabalho que coordena as outras instâncias de taskToRunWhenLeaseAcquired trabalho. Se a locação não for adquirida, outra instância de trabalhador foi eleita como líder e a instância de trabalhador atual permanece subordinada. Note que o TryAcquireLeaseOrWait método é um método auxiliar que utiliza o BlobLeaseManager objeto para adquirir o arrendamento.
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);
...
}
}
}
...
}
A tarefa iniciada pelo líder também é executada de forma assíncrona. Enquanto essa tarefa está em execução, o RunTaskWhenBlobLeaseAcquired método mostrado no exemplo de código a seguir tenta renovar periodicamente a concessão. Isso ajuda a garantir que a instância do trabalhador permaneça a líder. Na solução de exemplo, o atraso entre as solicitações de renovação é menor do que o tempo especificado para a duração da locação, a fim de evitar que outra instância de trabalhador seja eleita líder. Se a renovação falhar por qualquer motivo, a tarefa específica do líder é cancelada.
Se a concessão não for renovada ou a tarefa for cancelada (possivelmente como resultado do desligamento da instância de trabalho), a concessão será liberada. Neste ponto, esta ou outra instância do trabalhador pode ser eleita como líder. O seguinte extrato de código mostra esta parte do processo.
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);
}
}
}
}
...
}
O KeepRenewingLease método é outro método auxiliar que usa o BlobLeaseManager objeto para renovar a concessão. O CancelAllWhenAnyCompletes método cancela as tarefas especificadas como os dois primeiros parâmetros. O diagrama a seguir ilustra o uso da BlobDistributedMutex classe para eleger um líder e executar uma tarefa que coordena operações.
O exemplo de código a seguir mostra como usar a classe dentro de BlobDistributedMutex uma instância de trabalho. Esse código adquire uma concessão sobre um blob nomeado MyLeaderCoordinatorTask no contêiner da concessão Armazenamento de Blobs do Azure e especifica que o código definido no MyLeaderCoordinatorTask método deve ser executado se a instância de trabalho for eleita como líder.
// 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)
{
...
}
Observe os seguintes pontos sobre a solução de exemplo:
- O blob é um potencial ponto único de falha. Se o serviço de blob ficar indisponível ou estiver inacessível, o líder não poderá renovar a locação e nenhuma outra instância de trabalhador poderá adquirir a locação. Neste caso, nenhuma instância de trabalhador será capaz de atuar como líder. No entanto, o serviço de blob foi projetado para ser resiliente, portanto, a falha completa do serviço de blob é considerada extremamente improvável.
- Se a tarefa que está a ser executada pelo líder parar, o líder pode continuar a renovar o contrato, impedindo que qualquer outra instância de trabalhador adquira o contrato de arrendamento e assuma a posição de líder para coordenar tarefas. No mundo real, a saúde do líder deve ser verificada em intervalos frequentes.
- O processo eleitoral não é determinista. Você não pode fazer suposições sobre qual instância de trabalhador adquirirá o contrato de arrendamento de blob e se tornará o líder.
- O blob usado como alvo da locação de blob não deve ser usado para qualquer outra finalidade. Se uma instância de trabalho tentar armazenar dados nesse blob, esses dados não estarão acessíveis a menos que a instância de trabalho seja o líder e mantenha a concessão de blob.
Próximos passos
As seguintes orientações também podem ser relevantes na implementação deste padrão:
- Esse padrão tem um aplicativo de exemplo para download.
- Orientações de Dimensionamento Automático. É possível iniciar e parar instâncias dos hosts de tarefas à medida que a carga no aplicativo varia. O dimensionamento automático pode ajudar a manter a taxa de transferência e o desempenho durante os momentos de pico de processamento.
- O padrão assíncrono baseado em tarefas.
- Um exemplo que ilustra o algoritmo Bully.
- Um exemplo que ilustra o algoritmo de anel.
- Apache Curator , uma biblioteca cliente para o Apache ZooKeeper.
- O artigo Lease Blob (REST API) no MSDN.