Los sistemas distribuidos han estado presentes durante un tiempo y ya se han establecido patrones bien conocidos al diseñarlos. Hoy, discutiremos uno de los patrones populares: “bloqueos.”
En términos simples, los bloqueos son la forma en que los procesos obtienen acceso exclusivo a un recurso para realizar una cierta acción. Por ejemplo, imagina que hay un montón de blobs en una cuenta de almacenamiento, y necesitas que una instancia de tu servicio procese cada blob para evitar el procesamiento duplicado. La forma de hacerlo sería adquirir un bloqueo en el blob, completar el procesamiento y liberarlo. Sin embargo, surge un problema potencial si un proceso falla antes de liberar el bloqueo, ya sea porque el proceso se detuvo o debido a una partición de red, dejando el recurso bloqueado indefinidamente. Esto puede llevar a interbloqueos y contención de recursos.
Para prevenir interbloqueos, una estrategia que se puede emplear es usar bloqueos basados en tiempos de espera o arrendamientos.
Bloqueo por tiempo de espera
- En este caso, hay un tiempo de espera predefinido por el cual el proceso solicita el bloqueo. Si el bloqueo no se libera antes de que se agote el tiempo, el sistema asegura que el bloqueo se libere eventualmente.
Bloqueo por arrendamiento
-
Para los bloqueos basados en arrendamiento, se proporciona una API de renovación de arrendamiento junto con el mecanismo de tiempo de espera. El proceso que posee el bloqueo debe llamar a esta API antes de que expire el arrendamiento para mantener el acceso exclusivo al recurso. Si el proceso no logra renovar el arrendamiento a tiempo, el bloqueo se libera automáticamente, permitiendo que otros procesos lo adquieran.
Pros y Contras de los Bloqueos Basados en Tiempo de Espera y Arrendamiento
Pros | Cons | |
---|---|---|
Bloqueo basado en tiempo de espera | Fácil de implementar | Requiere una selección cuidadosa del tiempo de espera |
Previene bloqueos permanentes | Si el procesamiento no está completo, entonces no hay forma de renovar el arrendamiento | |
Bloqueo basado en arrendamiento | Reduce el riesgo de expiración prematura del bloqueo |
Requiere un mecanismo para la renovación del arrendamiento |
El proceso puede continuar solicitando el arrendamiento hasta que el trabajo esté completo. |
Ambas estrategias anteriores son una forma de recuperarse rápidamente de fallos de proceso o particiones de red en sistemas distribuidos.
Estrategia de Bloqueo de Arrendamiento con Azure Storage
Vamos a ver cómo utilizar la estrategia de Bloqueo de Arrendamiento con Azure Storage. Esto también cubre la estrategia de Bloqueo de Tiempo.
Paso 1: Importar el Paquete de Almacenamiento de Blob
“12.23.0” es la última versión en el momento de la redacción de este artículo. Las últimas versiones se pueden encontrar en Almacenamiento de Blobs de Azure.
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
</ItemGroup>
Paso 2: Adquirir el Arrendamiento
A continuación se muestra el código para adquirir el arrendamiento.
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;
}
}
- Primero, creamos un Cliente de Contenedor de Blob y recuperamos el Cliente de Blob para el blob específico en el que queremos adquirir un arrendamiento.
- En segundo lugar, el método “Acquire Async” intenta adquirir el arrendamiento por una duración específica. Si la adquisición fue exitosa, se devuelve un Id de arrendamiento; de lo contrario, se lanza un 409 (código de estado para conflicto).
- El “Acquire Async” es el método clave aquí. El resto del código se puede adaptar/editar según sus necesidades.
Paso 3: Renovar el Arrendamiento
- “Renew Async” es el método en el SDK de .NET de Almacenamiento utilizado para renovar el arrendamiento.
- Si la renovación no tiene éxito, se lanza una excepción junto con la razón de la causa del fallo.
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.RenewAsync().ConfigureAwait(false);
}
Paso 4: Orquestar los Métodos de Adquirir y Renovar Arrendamiento
- Inicialmente, llamamos a “Intentar Adquirir Arrendamiento Asíncrono” para obtener el identificador de arrendamiento del Paso 2. Una vez que es exitoso, se inicia una tarea en segundo plano que llama a “Renovar Arrendamiento Asíncrono” del Paso 3 cada X segundos. Solo asegúrate de que haya suficiente tiempo entre el tiempo de espera y cuando se llama al método de renovación del arrendamiento.
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);
- El token de cancelación se utiliza para detener el proceso de renovación del arrendamiento de manera controlada cuando ya no es necesario.
Paso 5: Cancelar la Renovación del Arrendamiento
- Cuando se llama al método “Cancelar Asíncrono”, “IsCancellationRequested” en el Paso 4 se convierte en verdadero, por lo que ya no entramos en el bucle while y solicitamos la renovación del arrendamiento.
await cancellationTokenSource.CancelAsync().ConfigureAwait(false);
await leaseRenwerTask.WaitAsync(Timeout.InfiniteTimeSpan).ConfigureAwait(false);
Paso 6: Liberar el Arrendamiento
Finalmente, para liberar el arrendamiento, simplemente llama al método “Liberar Asíncrono”.
public async Task ReleaseLeaseAsync(string blobName, string leaseId)
{
BlobLeaseClient blobLeaseClient = this.blobContainerClient.GetBlobClient(blobName).GetBlobLeaseClient(leaseId);
await blobLeaseClient.ReleaseAsync().ConfigureAwait(false);
}
Conclusión
Los bloqueos son uno de los patrones fundamentales en sistemas distribuidos para obtener acceso exclusivo a recursos. Es necesario tener en cuenta las trampas al tratar con ellos para el buen funcionamiento de las operaciones. Al utilizar Azure Storage, podemos implementar estos mecanismos de bloqueo eficientes que pueden prevenir bloqueos indefinidos y, al mismo tiempo, proporcionar elasticidad en la forma en que se mantienen los bloqueos.
Source:
https://dzone.com/articles/locks-in-distributed-systems-timeout-lease-based