

# Bloqueo distribuido con el cliente de bloqueo de DynamoDB
<a name="BestPractices_DistributedLocking"></a>

Para las aplicaciones que requieren la semántica tradicional de bloqueo, adquisición y liberación, el cliente de bloqueo de DynamoDB es una biblioteca de código abierto que implementa el bloqueo distribuido mediante una tabla de DynamoDB como almacén de bloqueos. Este enfoque resulta útil cuando se necesita coordinar el acceso a un recurso externo (como un objeto de S3 o una configuración compartida) en varias instancias de aplicaciones.

El cliente de bloqueo está disponible como una [biblioteca Java](https://github.com/awslabs/amazon-dynamodb-lock-client) de código abierto.

## Funcionamiento
<a name="BestPractices_DistributedLocking_HowItWorks"></a>

El cliente de bloqueo utiliza una tabla de DynamoDB dedicada para realizar un seguimiento de los bloqueos. Cada bloqueo se representa como un elemento con los siguientes atributos de claves:
+ Una clave de partición que identifica el recurso que se está bloqueando.
+ La duración de una asignación que especifica cuánto tiempo es válido el bloqueo. Si el titular del bloqueo se bloquea o deja de responder, el bloqueo caduca automáticamente una vez finalizada la asignación.
+ Un latido que el titular del bloqueo envía periódicamente para extender la asignación. Esto evita que el bloqueo caduque mientras el titular lo esté procesando activamente.

El cliente de bloqueo utiliza escrituras condicionales para garantizar que solo un proceso pueda adquirir un bloqueo a la vez. Si ya se ha mantenido un bloqueo, la persona que llama puede optar por esperar y volver a intentarlo o que se produzca un error inmediatamente.

## Cuándo usar el cliente de bloqueo
<a name="BestPractices_DistributedLocking_WhenToUse"></a>

El cliente de bloqueo es una buena opción cuando:
+ Debe coordinar el acceso a un recurso compartido entre varias instancias de aplicaciones o microservicios.
+ La parte crítica es de larga duración (de segundos a minutos) y volver a intentar toda la operación en caso de conflicto resultaría caro.
+ Es necesario que el bloqueo caduque automáticamente para gestionar correctamente los errores del proceso.

Algunos ejemplos comunes incluyen la orquestación de flujos de trabajo distribuidos, la coordinación de trabajos cron en varias instancias y la administración del acceso a recursos externos compartidos.

## Desventajas
<a name="BestPractices_DistributedLocking_Tradeoffs"></a>

**Infraestructura adicional**  
Requiere una tabla de DynamoDB dedicada para la administración de bloqueos, con capacidad adicional de lectura y escritura para las operaciones de bloqueo y los latidos.

**Dependencia del reloj**  
La caducidad del bloqueo depende de las marcas temporales. Un sesgo horario significativo entre los clientes puede provocar un comportamiento inesperado, especialmente en el caso de asignaciones de corta duración.

**Riesgo de bloqueo**  
Si la aplicación se bloquea en varios recursos, debe adquirirlos en un orden coherente para evitar bloqueos. La duración de la asignación proporciona una red de seguridad al liberar automáticamente los bloqueos de los titulares que no respondan.

## Implementación
<a name="BestPractices_DistributedLocking_Implementation"></a>

En el siguiente ejemplo, se muestra cómo utilizar el cliente de bloqueo de DynamoDB para adquirir y liberar un bloqueo:

```
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

final DynamoDbClient dynamoDB = DynamoDbClient.builder()
    .region(Region.US_WEST_2)
    .build();

final AmazonDynamoDBLockClient lockClient = new AmazonDynamoDBLockClient(
    AmazonDynamoDBLockClientOptions.builder(dynamoDB, "Locks")
        .withTimeUnit(TimeUnit.SECONDS)
        .withLeaseDuration(10L)
        .withHeartbeatPeriod(3L)
        .withCreateHeartbeatBackgroundThread(true)
        .build());

// Try to acquire a lock on a resource
final Optional<LockItem> lock =
    lockClient.tryAcquireLock(AcquireLockOptions.builder("my-shared-resource").build());

if (lock.isPresent()) {
    try {
        // Perform operations that require exclusive access
        processSharedResource();
    } finally {
        // Always release the lock when done
        lockClient.releaseLock(lock.get());
    }
} else {
    System.out.println("Failed to acquire lock.");
}

lockClient.close();
```

**importante**  
Libere siempre los bloqueos en un bloque `finally` para garantizar que se liberen incluso si la lógica de procesamiento genera una excepción. Los bloqueos no liberados bloquean otros procesos hasta que venza la asignación.

También puede implementar un mecanismo de bloqueo simple sin la biblioteca de cliente de bloqueo mediante el uso directo de escrituras condicionales. En el siguiente ejemplo, se utiliza `UpdateItem` con una expresión de condición para adquirir un bloqueo y `DeleteItem` para liberarlo:

```
from datetime import datetime, timedelta
from boto3.dynamodb.conditions import Attr

def acquire_lock(table, resource_name, owner_id, ttl_seconds):
    """Attempt to acquire a lock. Returns True if successful."""
    expiry = (datetime.now() + timedelta(seconds=ttl_seconds)).isoformat()
    now = datetime.now().isoformat()
    try:
        table.update_item(
            Key={'LockID': resource_name},
            UpdateExpression='SET #owner = :owner, #expiry = :expiry',
            ConditionExpression=Attr('LockID').not_exists() | Attr('ExpiresAt').lt(now),
            ExpressionAttributeNames={'#owner': 'OwnerID', '#expiry': 'ExpiresAt'},
            ExpressionAttributeValues={':owner': owner_id, ':expiry': expiry}
        )
        return True
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        return False

def release_lock(table, resource_name, owner_id):
    """Release a lock. Only succeeds if the caller is the lock owner."""
    try:
        table.delete_item(
            Key={'LockID': resource_name},
            ConditionExpression=Attr('OwnerID').eq(owner_id)
        )
        return True
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        return False
```

Este enfoque utiliza una expresión de condición para garantizar que un bloqueo solo se pueda adquirir si no existe o ha caducado, y solo se puede liberar mediante el proceso que lo adquirió. Considere la posibilidad de activar [Periodo de vida (TTL)](TTL.md) en la tabla de bloqueos para limpiar automáticamente los elementos de bloqueo caducados.