

# Bloqueio distribuído com o DynamoDB Lock Client
<a name="BestPractices_DistributedLocking"></a>

Para aplicações que exigem a semântica tradicional de aquisição e liberação de bloqueios, o DynamoDB Lock Client é uma biblioteca de código aberto que implementa o bloqueio distribuído usando uma tabela do DynamoDB como armazenamento de bloqueios. Essa abordagem é útil quando é necessário coordenar o acesso a um recurso externo (como um objeto do S3 ou uma configuração compartilhada) em várias instâncias da aplicação.

O cliente de bloqueio está disponível como uma [biblioteca Java](https://github.com/awslabs/amazon-dynamodb-lock-client) de código aberto.

## Como funciona
<a name="BestPractices_DistributedLocking_HowItWorks"></a>

O cliente de bloqueio usa uma tabela dedicada do DynamoDB para rastrear bloqueios. Cada bloqueio é representado como um item com os seguintes atributos de chave:
+ Uma chave de partição que identifica o recurso que está sendo bloqueado.
+ A duração da concessão que especifica por quanto tempo o bloqueio é válido. Se o detentor do bloqueio falhar ou deixar de responder, o bloqueio expirará automaticamente após a duração da concessão.
+ Um heartbeat que o detentor do bloqueio envia periodicamente para estender a concessão. Isso evita que o bloqueio expire enquanto o detentor ainda está processando ativamente.

O cliente de bloqueio usa gravações condicionais para garantir que somente um processo possa adquirir um bloqueio por vez. Se um bloqueio já estiver retido, o chamador poderá optar por esperar e tentar novamente ou antecipar-se à falha imediatamente.

## Quando usar o cliente de bloqueio
<a name="BestPractices_DistributedLocking_WhenToUse"></a>

O cliente de bloqueio é uma boa opção quando:
+ É necessário coordenar o acesso a um recurso compartilhado em várias instâncias de aplicação ou microsserviços.
+ A seção essencial for de longa duração (de segundos a minutos) e tentar novamente toda a operação em caso de conflito for caro.
+ Você precisa da expiração automática de bloqueio para lidar com as falhas do processo normalmente.

Exemplos comuns incluem orquestrar fluxos de trabalho distribuídos, coordenar tarefas cron em várias instâncias e gerenciar o acesso a recursos externos compartilhados.

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

**Infraestrutura adicional**  
Requer uma tabela do DynamoDB dedicada para gerenciamento de bloqueios, com capacidade adicional de leitura e gravação para operações de bloqueio e heartbeats.

**Dependência de clock**  
A expiração do bloqueio depende de carimbos de data/hora. Uma distorção de clock significativa entre os clientes pode causar um comportamento inesperado, especialmente para períodos curtos de concessão.

**Risco de deadlock**  
Se a aplicação adquirir bloqueios em vários recursos, será necessário adquiri-los em uma ordem consistente para evitar deadlocks. A duração da concessão oferece uma proteção ao liberar automaticamente os bloqueios dos detentores que não respondem.

## Implementação
<a name="BestPractices_DistributedLocking_Implementation"></a>

O exemplo a seguir mostra como usar o DynamoDB Lock Client para adquirir e liberar um bloqueio:

```
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**  
Sempre libere os bloqueios em um bloco `finally` para garantir que eles sejam liberados mesmo que a lógica de processamento gere uma exceção. Bloqueios não liberados impedem outros processos enquanto a concessão não expira.

Também é possível implementar um mecanismo de bloqueio simples sem a biblioteca de clientes de bloqueio usando gravações condicionais diretamente. O exemplo a seguir usa `UpdateItem` com uma expressão de condição para adquirir um bloqueio e `DeleteItem` para liberá-lo:

```
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
```

Essa abordagem usa uma expressão de condição para garantir que um bloqueio só possa ser adquirido se não existir ou tiver expirado, e que só possa ser liberado pelo processo que o adquiriu. Considere a possibilidade de habilitar a [vida útil (TTL)](TTL.md) na tabela de bloqueio para limpar automaticamente os itens de bloqueio expirados.