Blocco distribuito con il DynamoDB Lock Client - Amazon DynamoDB

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Blocco distribuito con il DynamoDB Lock Client

Per le applicazioni che richiedono la lock-acquire-release semantica tradizionale, DynamoDB Lock Client è una libreria open source che implementa il blocco distribuito utilizzando una tabella DynamoDB come archivio di blocco. Questo approccio è utile quando è necessario coordinare l'accesso a una risorsa esterna (come un oggetto S3 o una configurazione condivisa) tra più istanze dell'applicazione.

Il lock client è disponibile come libreria Java open source.

Come funziona

Il lock client utilizza una tabella DynamoDB dedicata per tracciare i blocchi. Ogni blocco è rappresentato come un elemento con i seguenti attributi chiave:

  • Una chiave di partizione che identifica la risorsa da bloccare.

  • Una durata del leasing che specifica per quanto tempo è valido il blocco. Se il titolare del lucchetto si blocca o non risponde, il lucchetto scade automaticamente dopo la durata del leasing.

  • Un battito cardiaco che il titolare della serratura invia periodicamente per prolungare il contratto di locazione. In questo modo si evita che il lucchetto scada mentre il titolare è ancora in fase di elaborazione attiva.

Il lock client utilizza scritture condizionali per garantire che un solo processo possa acquisire un blocco alla volta. Se è già presente un blocco, il chiamante può scegliere di attendere e riprovare o fallire immediatamente.

Quando usare il lock client

Il lock client è adatto quando:

  • È necessario coordinare l'accesso a una risorsa condivisa tra più istanze di applicazioni o microservizi.

  • La sezione critica richiede tempi lunghi (da secondi a minuti) e riprovare l'intera operazione in caso di conflitto sarebbe costoso.

  • È necessaria la scadenza automatica del blocco per gestire con garbo gli errori del processo.

Gli esempi più comuni includono l'orchestrazione di flussi di lavoro distribuiti, il coordinamento dei cron job su più istanze e la gestione dell'accesso a risorse esterne condivise.

Compromessi

Infrastruttura aggiuntiva

Richiede una tabella DynamoDB dedicata per la gestione dei blocchi, con capacità di lettura e scrittura aggiuntiva per le operazioni di blocco e gli heartbeat.

Dipendenza dall'orologio

La scadenza del lucchetto dipende dai timestamp. Una significativa differenza di clock tra i clienti può causare comportamenti imprevisti, in particolare per periodi di leasing di breve durata.

Rischio di stallo

Se l'applicazione acquisisce blocchi su più risorse, è necessario acquisirli in un ordine coerente per evitare situazioni di stallo. La durata del leasing fornisce una rete di sicurezza sbloccando automaticamente le serrature dai possessori che non rispondono.

Implementazione

L'esempio seguente mostra come utilizzare il DynamoDB Lock Client per acquisire e rilasciare un blocco:

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

Rilascia sempre i blocchi in un finally blocco per garantire che i blocchi vengano rilasciati anche se la logica di elaborazione genera un'eccezione. I blocchi non rilasciati bloccano altri processi fino alla scadenza del contratto di locazione.

È inoltre possibile implementare un semplice meccanismo di blocco senza la libreria lock client utilizzando direttamente le scritture condizionali. L'esempio seguente utilizza UpdateItem un'espressione condizionale per acquisire un blocco e DeleteItem rilasciarlo:

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

Questo approccio utilizza un'espressione di condizione per garantire che un blocco possa essere acquisito solo se non esiste o è scaduto e può essere rilasciato solo dal processo che lo ha acquisito. Valuta la possibilità di abilitare Time to Live (TTL) sulla tabella di blocco per ripulire automaticamente gli elementi bloccati scaduti.