Sistemas distribuídos existem há algum tempo e já há padrões bem conhecidos estabelecidos ao projetá-los. Hoje, discutiremos um dos padrões populares: “locks.”
De forma simples, locks são como os processos obtêm acesso exclusivo a um recurso para realizar uma certa ação. Por exemplo, imagine que existem vários Blobs em uma conta de armazenamento, e você precisa que uma instância do seu serviço processe cada blob para evitar processamento duplicado. A maneira de fazer isso seria adquirir um lock no blob, completar o processamento e liberá-lo. No entanto, um problema potencial surge se um processo falhar antes de liberar o lock, seja porque o processo morreu ou devido a uma partição de rede, deixando o recurso bloqueado indefinidamente. Isso pode levar a deadlocks e contenção de recursos.
Para prevenir deadlocks, uma estratégia que pode ser empregada é usar timeouts ou locks baseados em leasing.
Timeout Lock
- Neste caso, há um timeout pré-definido que o processo solicita para o lock. Se o lock não for liberado antes do timeout, o sistema garante que o lock seja eventualmente liberado.
Lease Lock
-
Para travas baseadas em locação, é fornecida uma API de renovação de locação juntamente com o mecanismo de tempo limite. O processo que detém a trava deve chamar essa API antes que a locação expire para manter acesso exclusivo ao recurso. Se o processo falhar em renovar a locação a tempo, a trava é automaticamente liberada, permitindo que outros processos a adquiram.
Prós e contras de travas baseadas em tempo limite e locação
Pros | Cons | |
---|---|---|
Trava baseada em tempo limite | Fácil de implementar | Requer seleção cuidadosa do tempo limite |
Previne travamentos permanentes | Se o processamento não estiver completo, então não há maneira de renovar a locação | |
Trava baseada em locação | Reduz o risco de expiração prematura da trava | Requer mecanismo para renovação da locação |
O processo pode continuar a solicitar a locação até que o trabalho seja concluído. |
Ambas as estratégias acima são formas de se recuperar rapidamente de falhas de processos ou partições de rede em sistemas distribuídos.
Estratégia de Bloqueio de Locação com Armazenamento Azure
Vamos ver como usar a estratégia de Bloqueio de Locação com o Armazenamento Azure. Isso também abrange a estratégia de Bloqueio de Tempo Limite.
Passo 1: Importe o Nuget de Blob de Armazenamento
“12.23.0” é a versão mais recente no momento da redação deste artigo. As versões mais recentes podem ser encontradas em Blobs de Armazenamento Azure.
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
</ItemGroup>
Passo 2: Adquirir a Locação
Abaixo está o código para adquirir a locação.
public async Task<string> TryAcquireLeaseAsync(string blobName, TimeSpan durationInSeconds, string leaseId = default)
{
BlobContainerClient blobContainerClient = new BlobContainerClient(new Uri($"https://{storageName}.blob.core.windows.net/processors"), tokenCredential, blobClientOptions);
BlobLeaseClient blobLeaseClient = blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
try
{
BlobLease lease = await blobLeaseClient.AcquireAsync(durationInSeconds).ConfigureAwait(false);
return lease.LeaseId;
}
catch (RequestFailedException ex) when (ex.Status == 409)
{
return default;
}
}
- Primeiramente, criamos um Cliente de Contêiner de Blob e recuperamos o Cliente de Blob para o blob específico que desejamos adquirir uma locação.
- Em segundo lugar, o método “Acquire Async” tenta adquirir a locação por uma duração específica. Se a aquisição for bem-sucedida, um Id de locação é retornado, caso contrário, é lançado um 409 (Código de status para conflito).
- O “Acquire Async” é o método chave aqui. O restante do código pode ser adaptado/editado conforme suas necessidades.
Passo 3: Renovar a Locação
- “Renew Async” é o método no SDK .NET de Armazenamento usado para renovar a locação.
- Se a renovação for mal sucedida, uma exceção é lançada juntamente com o motivo da falha.
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.RenewAsync().ConfigureAwait(false);
}
Passo 4: Orquestrar os Métodos de Adquirir e Renovar Locação
- Inicialmente, chamamos o “Tentar Adquirir Licença Assíncrona” para buscar o identificador da licença da Etapa 2. Uma vez que é bem-sucedido, uma tarefa em segundo plano é iniciada que chama o “Renovar Licença Assíncrona” da Etapa 3 a cada X segundos. Apenas certifique-se de que haja tempo suficiente entre o tempo limite e quando o método de renovação da licença é chamado.
string leaseId = await this.blobReadProcessor.TryAcquireLeaseAsync(blobName, TimeSpan.FromSeconds(60)).ConfigureAwait(false);
Task leaseRenwerTask = this.taskFactory.StartNew(
async () =>
{
while (leaseId != default && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(renewLeaseMillis).ConfigureAwait(false);
await this.blobReadProcessor.RenewLeaseAsync(blobName, leaseId).ConfigureAwait(false);
}
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
- O token de cancelamento é usado para parar graciosamente a tarefa de renovação da licença quando não é mais necessário.
Etapa 5: Cancelar a Renovação da Licença
- Quando o método “Cancelar Assíncrono” é chamado, o “IsCancellationRequested” na Etapa 4 se torna verdadeiro, por causa do qual não entramos mais no loop while e solicitamos a renovação da licença.
await cancellationTokenSource.CancelAsync().ConfigureAwait(false);
await leaseRenwerTask.WaitAsync(Timeout.InfiniteTimeSpan).ConfigureAwait(false);
Etapa 6: Liberar a Licença
Finalmente, para liberar a licença, basta chamar o método “Liberar Assíncrono”.
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.ReleaseAsync().ConfigureAwait(false);
}
Conclusão
Os bloqueios estão entre os padrões fundamentais em sistemas distribuídos para obter acesso exclusivo a recursos. É necessário ter em mente as armadilhas ao lidar com eles para o bom funcionamento das operações. Ao usar Azure Storage, podemos implementar esses mecanismos de bloqueio eficientes que podem prevenir bloqueios indefinidos e, ao mesmo tempo, fornecer elasticidade em como os bloqueios são mantidos.
Source:
https://dzone.com/articles/locks-in-distributed-systems-timeout-lease-based