View a markdown version of this page

Blocco distribuito con 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 DynamoDB Lock Client

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

Il lock client è disponibile come libreria Java open source.

Come funziona

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

  • Una chiave di partizione che identifica la risorsa bloccata.

  • Una durata del lease 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.

Il lock client utilizza scritture condizionali per assicurarsi che solo un processo possa acquisire un blocco alla volta. Se un blocco è già bloccato, 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 è di lunga durata (da secondi a minuti) e ritentare l'intera operazione in caso di conflitto sarebbe costoso.

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

Esempi 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 i battiti cardiaci.

Dipendenza dall'orologio

La scadenza del blocco dipende dai timestamp. Una notevole variazione dell'orario tra i clienti può causare comportamenti imprevisti, in particolare per contratti di locazione di breve durata.

Rischio di stallo

Se l'applicazione acquisisce blocchi su più risorse, è necessario acquisirli in un ordine coerente per evitare i deadlock. La durata del leasing fornisce una rete di sicurezza grazie allo sblocco automatico dei lucchetti dei titolari che non rispondono.

Implementazione

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

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 assicurarti 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 leasing.

È 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 condizionale per assicurarsi 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) nella tabella dei lucchetti per ripulire automaticamente gli elementi di blocco scaduti.