Class AmazonDynamoDBLockClient
- java.lang.Object
-
- com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient
-
- All Implemented Interfaces:
Closeable,AutoCloseable,Runnable
@ThreadSafe public class AmazonDynamoDBLockClient extends Object implements Runnable, Closeable
Provides a simple library for using DynamoDB's consistent read/write feature to use it for managing distributed locks.
In order to use this library, the client must create a table in DynamoDB, although the library provides a convenience method for creating that table (createLockTableInDynamoDB.)
Here is some example code for how to use the lock client for leader election to work on a resource called "database-3" (it assumes you already have a DynamoDB table named lockTable, which can be created with the static
createLockTableInDynamoDBhelper method):AmazonDynamoDBLockClient lockClient = new AmazonDynamoDBLockClient( AmazonDynamoDBLockClientOptions.builder(dynamoDBClient, "lockTable").build(); try { // Attempt to acquire the lock indefinitely, polling DynamoDB every 2 seconds for the lock LockItem lockItem = lockClient.acquireLock( AcquireLockOptions.builder("database-3") .withRefreshPeriod(120L) .withAdditionalTimeToWaitForLock(Long.MAX_VALUE / 2L) .withTimeUnit(TimeUnit.MILLISECONDS) .build()); if (!lockItem.isExpired()) { // do business logic, you can call lockItem.isExpired() to periodically check to make sure you still have the lock // the background thread will keep the lock valid for you by sending heartbeats (default is every 5 seconds) } } catch (LockNotGrantedException x) { // Should only be thrown if the lock could not be acquired for Long.MAX_VALUE / 2L milliseconds. }Here is an example that involves a bunch of workers getting customer IDs from a queue, taking a lock on that Customer ID, then releasing that lock when complete:
AmazonDynamoDBLockClient lockClient = new AmazonDynamoDBLockClient( AmazonDynamoDBLockClient.builder(dynamoDBClient, "lockTable").build(); while (true) { // Somehow find out about what work needs to be done String customerID = getCustomerIDFromQueue(); try { // Don't try indefinitely -- if someone else has a lock on this Customer ID, just move onto the next customer // (note that, if there is a lock on this customer ID, this method will still wait at least 20 seconds in order to be // able to determine // if that lock is stale) LockItem lockItem = lockClient.acquireLock(AcquireLockOptions.builder(customerID).build()); if (!lockItem.isExpired()) { // Perform operation on this customer } lockItem.close(); } catch (LockNotGrantedException x) { logger.info("We failed to acquire the lock for customer " + customerID, x); } }- Author:
- Sasha Slutsker, Alexander Patrikalakis
-
-
Field Summary
-
Constructor Summary
Constructors Constructor Description AmazonDynamoDBLockClient(AmazonDynamoDBLockClientOptions amazonDynamoDBLockClientOptions)Initializes an AmazonDynamoDBLockClient using the lock client options specified in the AmazonDynamoDBLockClientOptions object.
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description LockItemacquireLock(AcquireLockOptions options)Attempts to acquire a lock until it either acquires the lock, or a specifiedadditionalTimeToWaitForLockis reached.voidassertLockTableExists()Asserts that the lock table exists in DynamoDB.voidclose()Releases all of the locks by calling releaseAllLocks()static voidcreateLockTableInDynamoDB(CreateDynamoDBTableOptions createDynamoDBTableOptions)Creates a DynamoDB table with the right schema for it to be used by this locking library.Stream<LockItem>getAllLocksFromDynamoDB(boolean deleteOnRelease)Retrieves all the lock items from DynamoDB.Optional<LockItem>getLock(String key, Optional<String> sortKey)Finds out who owns the given lock, but does not acquire the lock.Optional<LockItem>getLockFromDynamoDB(GetLockOptions options)Retrieves the lock item from DynamoDB.Stream<LockItem>getLocksByPartitionKey(String key, boolean deleteOnRelease)Retrieves the locks with partition_key =key.booleanhasLock(String key, Optional<String> sortKey)Returns true if the client currently owns the lock with @param key and @param sortKey.booleanlockTableExists()Checks whether the lock table exists in DynamoDB.booleanreleaseLock(LockItem lockItem)Releases the given lock if the current user still has it, returning true if the lock was successfully released, and false if someone else already stole the lock.booleanreleaseLock(ReleaseLockOptions options)voidrun()Loops forever, sending hearbeats for all the locks this thread needs to keep track of.voidsendHeartbeat(LockItem lockItem)Sends a heartbeat to indicate that the given lock is still being worked on.voidsendHeartbeat(SendHeartbeatOptions options)Sends a heartbeat to indicate that the given lock is still being worked on.Optional<LockItem>tryAcquireLock(AcquireLockOptions options)Attempts to acquire lock.
-
-
-
Field Detail
-
SK_PATH_EXPRESSION_VARIABLE
protected static final String SK_PATH_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
PK_PATH_EXPRESSION_VARIABLE
protected static final String PK_PATH_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
PK_VALUE_EXPRESSION_VARIABLE
protected static final String PK_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
NEW_RVN_VALUE_EXPRESSION_VARIABLE
protected static final String NEW_RVN_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
LEASE_DURATION_PATH_VALUE_EXPRESSION_VARIABLE
protected static final String LEASE_DURATION_PATH_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
LEASE_DURATION_VALUE_EXPRESSION_VARIABLE
protected static final String LEASE_DURATION_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
RVN_PATH_EXPRESSION_VARIABLE
protected static final String RVN_PATH_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
RVN_VALUE_EXPRESSION_VARIABLE
protected static final String RVN_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
OWNER_NAME_PATH_EXPRESSION_VARIABLE
protected static final String OWNER_NAME_PATH_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
OWNER_NAME_VALUE_EXPRESSION_VARIABLE
protected static final String OWNER_NAME_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
DATA_PATH_EXPRESSION_VARIABLE
protected static final String DATA_PATH_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
DATA_VALUE_EXPRESSION_VARIABLE
protected static final String DATA_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
IS_RELEASED_PATH_EXPRESSION_VARIABLE
protected static final String IS_RELEASED_PATH_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
IS_RELEASED_VALUE_EXPRESSION_VARIABLE
protected static final String IS_RELEASED_VALUE_EXPRESSION_VARIABLE
- See Also:
- Constant Field Values
-
ACQUIRE_LOCK_THAT_DOESNT_EXIST_PK_CONDITION
protected static final String ACQUIRE_LOCK_THAT_DOESNT_EXIST_PK_CONDITION
-
ACQUIRE_LOCK_THAT_DOESNT_EXIST_PK_SK_CONDITION
protected static final String ACQUIRE_LOCK_THAT_DOESNT_EXIST_PK_SK_CONDITION
-
PK_EXISTS_AND_IS_RELEASED_CONDITION
protected static final String PK_EXISTS_AND_IS_RELEASED_CONDITION
-
PK_EXISTS_AND_SK_EXISTS_AND_IS_RELEASED_CONDITION
protected static final String PK_EXISTS_AND_SK_EXISTS_AND_IS_RELEASED_CONDITION
-
PK_EXISTS_AND_SK_EXISTS_AND_RVN_IS_THE_SAME_AND_IS_RELEASED_CONDITION
protected static final String PK_EXISTS_AND_SK_EXISTS_AND_RVN_IS_THE_SAME_AND_IS_RELEASED_CONDITION
-
PK_EXISTS_AND_SK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION
protected static final String PK_EXISTS_AND_SK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION
-
PK_EXISTS_AND_SK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION
protected static final String PK_EXISTS_AND_SK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION
-
PK_EXISTS_AND_RVN_IS_THE_SAME_AND_IS_RELEASED_CONDITION
protected static final String PK_EXISTS_AND_RVN_IS_THE_SAME_AND_IS_RELEASED_CONDITION
-
PK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION
protected static final String PK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION
-
PK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION
protected static final String PK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION
-
UPDATE_IS_RELEASED
protected static final String UPDATE_IS_RELEASED
-
UPDATE_IS_RELEASED_AND_DATA
protected static final String UPDATE_IS_RELEASED_AND_DATA
-
UPDATE_LEASE_DURATION_AND_RVN
protected static final String UPDATE_LEASE_DURATION_AND_RVN
-
UPDATE_LEASE_DURATION_AND_RVN_AND_REMOVE_DATA
protected static final String UPDATE_LEASE_DURATION_AND_RVN_AND_REMOVE_DATA
-
UPDATE_LEASE_DURATION_AND_RVN_AND_DATA
protected static final String UPDATE_LEASE_DURATION_AND_RVN_AND_DATA
-
REMOVE_IS_RELEASED_UPDATE_EXPRESSION
protected static final String REMOVE_IS_RELEASED_UPDATE_EXPRESSION
-
QUERY_PK_EXPRESSION
protected static final String QUERY_PK_EXPRESSION
-
dynamoDB
protected final software.amazon.awssdk.services.dynamodb.DynamoDbClient dynamoDB
-
tableName
protected final String tableName
-
DATA
protected static final String DATA
- See Also:
- Constant Field Values
-
OWNER_NAME
protected static final String OWNER_NAME
- See Also:
- Constant Field Values
-
LEASE_DURATION
protected static final String LEASE_DURATION
- See Also:
- Constant Field Values
-
RECORD_VERSION_NUMBER
protected static final String RECORD_VERSION_NUMBER
- See Also:
- Constant Field Values
-
IS_RELEASED
protected static final String IS_RELEASED
- See Also:
- Constant Field Values
-
IS_RELEASED_VALUE
protected static final String IS_RELEASED_VALUE
- See Also:
- Constant Field Values
-
IS_RELEASED_ATTRIBUTE_VALUE
protected static final software.amazon.awssdk.services.dynamodb.model.AttributeValue IS_RELEASED_ATTRIBUTE_VALUE
-
lockClientId
protected static volatile AtomicInteger lockClientId
-
IS_RELEASED_INDICATOR
protected static final Boolean IS_RELEASED_INDICATOR
-
-
Constructor Detail
-
AmazonDynamoDBLockClient
public AmazonDynamoDBLockClient(AmazonDynamoDBLockClientOptions amazonDynamoDBLockClientOptions)
Initializes an AmazonDynamoDBLockClient using the lock client options specified in the AmazonDynamoDBLockClientOptions object.- Parameters:
amazonDynamoDBLockClientOptions- The options to use when initializing the client, i.e. the table name, sort key value, etc.
-
-
Method Detail
-
lockTableExists
public boolean lockTableExists()
Checks whether the lock table exists in DynamoDB.- Returns:
- true if the table exists, false otherwise.
-
assertLockTableExists
public void assertLockTableExists() throws LockTableDoesNotExistExceptionAsserts that the lock table exists in DynamoDB. You can use this method during application initialization to ensure that the lock client will be usable. Since this is a no-arg assertion as opposed to a check that returns a value, this method is also suitable as an init-method for a Spring bean.- Throws:
LockTableDoesNotExistException- if the table doesn't exist.
-
createLockTableInDynamoDB
public static void createLockTableInDynamoDB(CreateDynamoDBTableOptions createDynamoDBTableOptions)
Creates a DynamoDB table with the right schema for it to be used by this locking library. The table should be set up in advance, because it takes a few minutes for DynamoDB to provision a new instance. Also, if the table already exists, this will throw an exception.This method lets you specify a sort key to be used by the lock client. This sort key then needs to be specified in the AmazonDynamoDBLockClientOptions when the lock client object is created.
- Parameters:
createDynamoDBTableOptions- The options for the lock client
-
acquireLock
public LockItem acquireLock(AcquireLockOptions options) throws LockNotGrantedException, InterruptedException
Attempts to acquire a lock until it either acquires the lock, or a specified
additionalTimeToWaitForLockis reached. This method will poll DynamoDB based on therefreshPeriod. If it does not see the lock in DynamoDB, it will immediately return the lock to the caller. If it does see the lock, it will note the lease expiration on the lock. If the lock is deemed stale, (that is, there is no heartbeat on it for at least the length of its lease duration) then this will acquire and return it. Otherwise, if it waits for as long asadditionalTimeToWaitForLockwithout acquiring the lock, then it will throw aLockNotGrantedException.Note that this method will wait for at least as long as the
leaseDurationin order to acquire a lock that already exists. If the lock is not acquired in that time, it will wait an additional amount of time specified inadditionalTimeToWaitForLockbefore giving up.See the defaults set when constructing a new
AcquireLockOptionsobject for any fields that you do not set explicitly.- Parameters:
options- A combination of optional arguments that may be passed in for acquiring the lock- Returns:
- the lock
- Throws:
LockNotGrantedException- if the lock is not acquired before the configured wait time elapses, or if the lock cannot be acquired because of a failed conditional write or exceeded provisioned throughput.LockCurrentlyUnavailableException- ifshouldSkipBlockingWaitis true and the lock is currently held by another owner, or if another owner acquires the lock first during a skip-blocking acquire attempt.InterruptedException- in case the Thread.sleep call was interrupted while waiting to refresh.
-
hasLock
public boolean hasLock(String key, Optional<String> sortKey)
Returns true if the client currently owns the lock with @param key and @param sortKey. It returns false otherwise.- Parameters:
key- The partition key representing the lock.sortKey- The sort key if present.- Returns:
- true if the client owns the lock. It returns false otherwise.
-
tryAcquireLock
public Optional<LockItem> tryAcquireLock(AcquireLockOptions options) throws InterruptedException
Attempts to acquire lock. If successful, returns the lock. Otherwise, returns Optional.empty(). This includes cases whereacquireLockwould throwLockNotGrantedExceptionorLockCurrentlyUnavailableException. For more details on behavior, please seeacquireLock.- Parameters:
options- The options to use when acquiring the lock.- Returns:
- the lock if successful.
- Throws:
InterruptedException- in case this.acquireLock was interrupted.
-
releaseLock
public boolean releaseLock(LockItem lockItem)
Releases the given lock if the current user still has it, returning true if the lock was successfully released, and false if someone else already stole the lock. Deletes the lock item if it is released and deleteLockItemOnClose is set.- Parameters:
lockItem- The lock item to release- Returns:
- true if the lock is released, false otherwise
-
releaseLock
public boolean releaseLock(ReleaseLockOptions options)
-
getLock
public Optional<LockItem> getLock(String key, Optional<String> sortKey)
Finds out who owns the given lock, but does not acquire the lock. It returns the metadata currently associated with the given lock. If the client currently has the lock, it will return the lock, and operations such as releaseLock will work. However, if the client does not have the lock, then operations like releaseLock will not work (after calling getLock, the caller should check lockItem.isExpired() to figure out if it currently has the lock.)- Parameters:
key- The partition key representing the lock.sortKey- The sort key if present.- Returns:
- A LockItem that represents the lock, if the lock exists.
-
getLockFromDynamoDB
public Optional<LockItem> getLockFromDynamoDB(GetLockOptions options)
Retrieves the lock item from DynamoDB. Note that this will return a LockItem even if it was released -- do NOT use this method if your goal is to acquire a lock for doing work.- Parameters:
options- The options such as the key, etc.- Returns:
- The LockItem, or absent if it is not present. Note that the item can exist in the table even if it is released, as noted by isReleased().
-
getAllLocksFromDynamoDB
public Stream<LockItem> getAllLocksFromDynamoDB(boolean deleteOnRelease)
Retrieves all the lock items from DynamoDB.
Not that this will may return a lock item even if it was released.
- Parameters:
deleteOnRelease- Whether or not theLockItemshould delete the item whenLockItem.close()is called on it.- Returns:
- A non parallel
Streamof all theLockItems in DynamoDB. Note that the item can exist in the table even if it is released, as noted byLockItem.isReleased().
-
getLocksByPartitionKey
public Stream<LockItem> getLocksByPartitionKey(String key, boolean deleteOnRelease)
Retrieves the locks with partition_key =
key.Not that this may return a lock item even if it was released.
- Parameters:
key- the partition keydeleteOnRelease- Whether or not theLockItemshould delete the item whenLockItem.close()is called on it.- Returns:
- A non parallel
StreamofLockItems that has the partition key in DynamoDB. Note that the item can exist in the table even if it is released, as noted byLockItem.isReleased().
-
sendHeartbeat
public void sendHeartbeat(LockItem lockItem)
Sends a heartbeat to indicate that the given lock is still being worked on. If using
createHeartbeatBackgroundThread=true when setting up this object, then this method is unnecessary, because the background thread will be periodically calling it and sending heartbeats. However, ifcreateHeartbeatBackgroundThread=false, then this method must be called to instruct DynamoDB that the lock should not be expired.The lease duration of the lock will be set to the default specified in the constructor of this class.
- Parameters:
lockItem- the lock item row to send a heartbeat and extend lock expiry.
-
sendHeartbeat
public void sendHeartbeat(SendHeartbeatOptions options)
Sends a heartbeat to indicate that the given lock is still being worked on. If using
createHeartbeatBackgroundThread=true when setting up this object, then this method is unnecessary, because the background thread will be periodically calling it and sending heartbeats. However, ifcreateHeartbeatBackgroundThread=false, then this method must be called to instruct DynamoDB that the lock should not be expired.This method will also set the lease duration of the lock to the given value.
This will also either update or delete the data from the lock, as specified in the options
- Parameters:
options- a set of optional arguments for how to send the heartbeat
-
run
public void run()
Loops forever, sending hearbeats for all the locks this thread needs to keep track of.
-
close
public void close() throws IOExceptionReleases all of the locks by calling releaseAllLocks()- Specified by:
closein interfaceAutoCloseable- Specified by:
closein interfaceCloseable- Throws:
IOException
-
-