@ThreadSafe
public class AmazonDynamoDBLockClient
extends java.lang.Object
implements java.lang.Runnable, java.io.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
createLockTableInDynamoDB helper 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);
}
}
| Modifier and Type | Field and Description |
|---|---|
protected static java.lang.String |
ACQUIRE_LOCK_THAT_DOESNT_EXIST_OR_IS_RELEASED_CONDITION |
protected static java.lang.String |
DATA |
protected static java.lang.String |
DATA_PATH_EXPRESSION_VARIABLE |
protected static java.lang.String |
DATA_VALUE_EXPRESSION_VARIABLE |
protected AmazonDynamoDB |
dynamoDB |
protected static java.lang.String |
IS_RELEASED |
protected static AttributeValue |
IS_RELEASED_ATTRIBUTE_VALUE |
protected static java.lang.String |
IS_RELEASED_PATH_EXPRESSION_VARIABLE |
protected static java.lang.String |
IS_RELEASED_VALUE |
protected static java.lang.String |
IS_RELEASED_VALUE_EXPRESSION_VARIABLE |
protected static java.lang.String |
LEASE_DURATION |
protected static java.lang.String |
LEASE_DURATION_PATH_VALUE_EXPRESSION_VARIABLE |
protected static java.lang.String |
LEASE_DURATION_VALUE_EXPRESSION_VARIABLE |
protected static java.util.concurrent.atomic.AtomicInteger |
lockClientId |
protected static java.lang.String |
NEW_RVN_VALUE_EXPRESSION_VARIABLE |
protected static java.lang.String |
OWNER_NAME |
protected static java.lang.String |
OWNER_NAME_PATH_EXPRESSION_VARIABLE |
protected static java.lang.String |
OWNER_NAME_VALUE_EXPRESSION_VARIABLE |
protected static java.lang.String |
PK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION |
protected static java.lang.String |
PK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION |
protected static java.lang.String |
PK_EXISTS_AND_SK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION |
protected static java.lang.String |
PK_EXISTS_AND_SK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION |
protected static java.lang.String |
PK_PATH_EXPRESSION_VARIABLE |
protected static java.lang.String |
RECORD_VERSION_NUMBER |
protected static java.lang.String |
RVN_PATH_EXPRESSION_VARIABLE |
protected static java.lang.String |
RVN_VALUE_EXPRESSION_VARIABLE |
protected static java.lang.String |
SK_PATH_EXPRESSION_VARIABLE |
protected java.lang.String |
tableName |
protected static java.lang.String |
UPDATE_IS_RELEASED |
protected static java.lang.String |
UPDATE_IS_RELEASED_AND_DATA |
protected static java.lang.String |
UPDATE_LEASE_DURATION_AND_RVN |
protected static java.lang.String |
UPDATE_LEASE_DURATION_AND_RVN_AND_DATA |
protected static java.lang.String |
UPDATE_LEASE_DURATION_AND_RVN_AND_REMOVE_DATA |
| Constructor and Description |
|---|
AmazonDynamoDBLockClient(AmazonDynamoDBLockClientOptions amazonDynamoDBLockClientOptions)
Initializes an AmazonDynamoDBLockClient using the lock client options
specified in the AmazonDynamoDBLockClientOptions object.
|
| Modifier and Type | Method and Description |
|---|---|
LockItem |
acquireLock(AcquireLockOptions options)
Attempts to acquire a lock until it either acquires the lock, or a specified
additionalTimeToWaitForLock is
reached. |
void |
assertLockTableExists()
Asserts that the lock table exists in DynamoDB.
|
void |
close()
Releases all of the locks by calling releaseAllLocks()
|
static void |
createLockTableInDynamoDB(CreateDynamoDBTableOptions createDynamoDBTableOptions)
Creates a DynamoDB table with the right schema for it to be used by this locking library.
|
java.util.stream.Stream<LockItem> |
getAllLocksFromDynamoDB(boolean deleteOnRelease)
Retrieves all the lock items from DynamoDB.
|
java.util.Optional<LockItem> |
getLock(java.lang.String key,
java.util.Optional<java.lang.String> sortKey)
Finds out who owns the given lock, but does not acquire the lock.
|
java.util.Optional<LockItem> |
getLockFromDynamoDB(GetLockOptions options)
Retrieves the lock item from DynamoDB.
|
boolean |
lockTableExists()
Checks whether the lock table exists in DynamoDB.
|
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.
|
boolean |
releaseLock(ReleaseLockOptions options) |
void |
run()
Loops forever, sending hearbeats for all the locks this thread needs to keep track of.
|
void |
sendHeartbeat(LockItem lockItem)
Sends a heartbeat to indicate that the given lock is still being worked on.
|
void |
sendHeartbeat(SendHeartbeatOptions options)
Sends a heartbeat to indicate that the given lock is still being worked on.
|
java.util.Optional<LockItem> |
tryAcquireLock(AcquireLockOptions options)
Attempts to acquire lock.
|
protected static final java.lang.String SK_PATH_EXPRESSION_VARIABLE
protected static final java.lang.String PK_PATH_EXPRESSION_VARIABLE
protected static final java.lang.String NEW_RVN_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String LEASE_DURATION_PATH_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String LEASE_DURATION_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String RVN_PATH_EXPRESSION_VARIABLE
protected static final java.lang.String RVN_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String OWNER_NAME_PATH_EXPRESSION_VARIABLE
protected static final java.lang.String OWNER_NAME_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String DATA_PATH_EXPRESSION_VARIABLE
protected static final java.lang.String DATA_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String IS_RELEASED_PATH_EXPRESSION_VARIABLE
protected static final java.lang.String IS_RELEASED_VALUE_EXPRESSION_VARIABLE
protected static final java.lang.String ACQUIRE_LOCK_THAT_DOESNT_EXIST_OR_IS_RELEASED_CONDITION
protected static final java.lang.String PK_EXISTS_AND_SK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION
protected static final java.lang.String PK_EXISTS_AND_SK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION
protected static final java.lang.String PK_EXISTS_AND_RVN_IS_THE_SAME_CONDITION
protected static final java.lang.String PK_EXISTS_AND_OWNER_NAME_SAME_AND_RVN_SAME_CONDITION
protected static final java.lang.String UPDATE_IS_RELEASED
protected static final java.lang.String UPDATE_IS_RELEASED_AND_DATA
protected static final java.lang.String UPDATE_LEASE_DURATION_AND_RVN
protected static final java.lang.String UPDATE_LEASE_DURATION_AND_RVN_AND_REMOVE_DATA
protected static final java.lang.String UPDATE_LEASE_DURATION_AND_RVN_AND_DATA
protected final AmazonDynamoDB dynamoDB
protected final java.lang.String tableName
protected static final java.lang.String DATA
protected static final java.lang.String OWNER_NAME
protected static final java.lang.String LEASE_DURATION
protected static final java.lang.String RECORD_VERSION_NUMBER
protected static final java.lang.String IS_RELEASED
protected static final java.lang.String IS_RELEASED_VALUE
protected static final AttributeValue IS_RELEASED_ATTRIBUTE_VALUE
protected static volatile java.util.concurrent.atomic.AtomicInteger lockClientId
public AmazonDynamoDBLockClient(AmazonDynamoDBLockClientOptions amazonDynamoDBLockClientOptions)
amazonDynamoDBLockClientOptions - The options to use when initializing the client, i.e. the
table name, sort key value, etc.public boolean lockTableExists()
public void assertLockTableExists()
throws LockTableDoesNotExistException
LockTableDoesNotExistException - if the table doesn't exist.public static void createLockTableInDynamoDB(CreateDynamoDBTableOptions createDynamoDBTableOptions)
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.
createDynamoDBTableOptions - The options for the lock clientpublic LockItem acquireLock(AcquireLockOptions options) throws LockNotGrantedException, java.lang.InterruptedException
Attempts to acquire a lock until it either acquires the lock, or a specified additionalTimeToWaitForLock is
reached. This method will poll DynamoDB based on the refreshPeriod. 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 as additionalTimeToWaitForLock without acquiring the
lock, then it will throw a LockNotGrantedException.
Note that this method will wait for at least as long as the leaseDuration in 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 in
additionalTimeToWaitForLock before giving up.
See the defaults set when constructing a new AcquireLockOptions object for any fields that you do not set
explicitly.
options - A combination of optional arguments that may be passed in for acquiring the lockjava.lang.InterruptedException - in case the Thread.sleep call was interrupted while waiting to refresh.LockNotGrantedExceptionpublic java.util.Optional<LockItem> tryAcquireLock(AcquireLockOptions options) throws java.lang.InterruptedException
acquireLock.options - The options to use when acquiring the lock.java.lang.InterruptedException - in case this.acquireLock was interrupted.public boolean releaseLock(LockItem lockItem)
lockItem - The lock item to releasepublic boolean releaseLock(ReleaseLockOptions options)
public java.util.Optional<LockItem> getLock(java.lang.String key, java.util.Optional<java.lang.String> sortKey)
key - The partition key representing the lock.sortKey - The sort key if present.public java.util.Optional<LockItem> getLockFromDynamoDB(GetLockOptions options)
options - The options such as the key, etc.public java.util.stream.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.
deleteOnRelease - Whether or not the LockItem should delete the item
when LockItem.close() is called on it.Stream of all the LockItems in
DynamoDB. Note that the item can exist in the table even if it is
released, as noted by @{link LockItem#isReleased()}.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, if
createHeartbeatBackgroundThread=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.
lockItem - the lock item row to send a heartbeat and extend lock expiry.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, if
createHeartbeatBackgroundThread=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
options - a set of optional arguments for how to send the heartbeatpublic void run()
run in interface java.lang.Runnablepublic void close()
throws java.io.IOException
close in interface java.io.Closeableclose in interface java.lang.AutoCloseablejava.io.IOException