Sistemas distribuídos existem há algum tempo e já há padrões bem conhecidos estabelecidos ao projetá-los. Hoje, vamos discutir um dos padrões populares: “locks.”
De maneira simples, locks são como os processos ganham acesso exclusivo a um recurso para realizar uma determinada ação. Por exemplo, imagine que há um monte de 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 evitar deadlocks, uma estratégia que pode ser empregada é usar timeouts ou locks baseados em leasing.
Timeout Lock
- Neste caso, há um timeout pré-definido para o qual o processo solicita o lock. Se o lock não for liberado antes do timeout, o sistema garante que o lock seja eventualmente liberado.
Lease Lock
-
Para bloqueios baseados em locação, uma API de renovação de locação é fornecida juntamente com o mecanismo de tempo limite. O processo que possui o bloqueio deve chamar esta API antes que a locação expire para manter o acesso exclusivo ao recurso. Se o processo falhar em renovar a locação a tempo, o bloqueio é automaticamente liberado, permitindo que outros processos o adquiram.
Prós e Contras de Bloqueios Baseados em Tempo Limite e Locação
Pros | Cons | |
---|---|---|
Bloqueio baseado em tempo limite | Simples de implementar | Requer seleção cuidadosa do tempo limite |
Previne bloqueios permanentes | Se o processamento não estiver completo, não há como renovar a locação | |
Bloqueio baseado em locação | Reduz o risco de expiração prematura do bloqueio |
Requer mecanismo para renovação da locação |
O processo pode continuar a solicitar a locação até que o trabalho esteja completo. |
Ambas as estratégias acima são uma forma de recuperar rapidamente de falhas de processo ou partições de rede em sistemas distribuídos.
Estratégia de Bloqueio de Arrendamento com Azure Storage
Vamos ver como usar a estratégia de Bloqueio de Arrendamento com o Azure Storage. Isso também abrange a estratégia de bloqueio de Timeout.
Passo 1: Importar o Nuget do Storage Blob
“12.23.0” é a versão mais recente no momento da redação deste artigo. As versões mais recentes podem ser encontradas em Azure Storage Blobs.
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
</ItemGroup>
Passo 2: Adquirir o Arrendamento
Abaixo está o código para adquirir o arrendamento.
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;
}
}
- Primeiro, criamos um Cliente de Contêiner Blob e recuperamos o Cliente Blob para o blob específico no qual queremos adquirir um arrendamento.
- Em segundo lugar, o método “Acquire Async” tenta adquirir o arrendamento por um período específico. Se a aquisição for bem-sucedida, um ID de arrendamento é retornado, caso contrário, um 409 (código de status para conflito) é gerado.
- O “Acquire Async” é o método chave aqui. O restante do código pode ser adaptado/editado conforme suas necessidades.
Passo 3: Renovar o Arrendamento
- “Renew Async” é o método no SDK .NET do Storage usado para renovar o arrendamento.
- Se a renovação não for bem-sucedida, uma exceção é gerada junto com a razão 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 Aquisição e Renovação do Arrendamento
- Inicialmente, chamamos o “Try Acquire Lease Async” para buscar o identificador do lease a partir da Etapa 2. Uma vez que isso é bem-sucedido, uma tarefa em segundo plano é iniciada que chama o “Renew Lease Async” da Etapa 3 a cada X segundos. Apenas certifique-se de que haja tempo suficiente entre o timeout e quando o método de renovação do lease é 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 do lease quando não for mais necessário.
Etapa 5: Cancelar a Renovação do Lease
- Quando o método “Cancel Async” é chamado, o “IsCancellationRequested” na Etapa 4 se torna verdadeiro, por causa do qual não entramos mais no loop while e não solicitamos a renovação do lease.
await cancellationTokenSource.CancelAsync().ConfigureAwait(false);
await leaseRenwerTask.WaitAsync(Timeout.InfiniteTimeSpan).ConfigureAwait(false);
Etapa 6: Liberar o Lease
Finalmente, para liberar o lease, basta chamar o método “Release Async”.
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.ReleaseAsync().ConfigureAwait(false);
}
Conclusão
Locks 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, proporcionar elasticidade na forma como os locks são mantidos.
Source:
https://dzone.com/articles/locks-in-distributed-systems-timeout-lease-based