Amazon DynamoDB Lock Client是基于DynamoDB构建的通用分布式锁定库。DynamoDB Lock Client支持细粒度和粗粒度锁定,锁定键可以是任意字符串。DynamoDB Lock Client是一个开源项目,得到了社区的支持。
用例
该Lock Client的一个常见用例是:假设您有一个分布式系统,需要定期针对用户执行一些常规操作,您要确保不同服务实例不会同时处理同一个用户。解决此问题的一种简单方法是编写一个可以锁定客户的系统,但是细粒度的锁定是一个难题。
另一个用例是领导者选举。如果您只希望一台机器成为master,则此Lock Client客户端是选择master的一种好方法。当master发生故障时,通过Lock Client可以重新选出新的master。
入门
该组件的pom依赖如下:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>dynamodb-lock-client</artifactId>
<version>1.2.0</version>
</dependency>
然后,您需要设置一个DynamoDB的表,该表具有一个名为key的哈希键。为了方便起见,AmazonDynamoDBLockClient类中有一个名为createLockTableInDynamoDB的静态方法可用于设置表,但也可以在AWS控制台中设置该表。该表应提前创建,因为DynamoDB需要花费几分钟的时间来配置该表。
以下是一些示例代码,可帮助您入门:
import java.io.IOException;
import java.net.URI;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
public class LockClientExample {
@Test
public void usageExample() throws InterruptedException, IOException {
// Inject client configuration to the builder like the endpoint and signing region
final DynamoDbClient dynamoDB = DynamoDbClient.builder()
.region(Region.US_WEST_2).endpointOverride(URI.create("http://localhost:4567"))
.build();
// Whether or not to create a heartbeating background thread
final boolean createHeartbeatBackgroundThread = true;
//build the lock client
final AmazonDynamoDBLockClient client = new AmazonDynamoDBLockClient(
AmazonDynamoDBLockClientOptions.builder(dynamoDB, "lockTable")
.withTimeUnit(TimeUnit.SECONDS)
.withLeaseDuration(10L)
.withHeartbeatPeriod(3L)
.withCreateHeartbeatBackgroundThread(createHeartbeatBackgroundThread)
.build());
//try to acquire a lock on the partition key "Moe"
final Optional<LockItem> lockItem =
client.tryAcquireLock(AcquireLockOptions.builder("Moe").build());
if (lockItem.isPresent()) {
System.out.println("Acquired lock! If I die, my lock will expire in 10 seconds.");
System.out.println("Otherwise, I will hold it until I stop heartbeating.");
client.releaseLock(lockItem.get());
} else {
System.out.println("Failed to acquire lock!");
}
client.close();
}
}
其他特性
自动心跳包发送
调用构造函数AmazonDynamoDBLockClient时,可以createHeartbeatBackgroundThread=true
像上例中那样指定,它将生成一个后台线程,该线程会不断更新锁上的记录版本号,以防止锁过期(只要您的JVM正在运行,您的锁就不会过期,直到您调用releaseLock()或lockItem.close()
设置获取锁的超时时间
您可以通过两种不同的方法获取锁:acquireLock或tryAcquireLock。两种方法之间的区别在于,如果未获取锁,则tryAcquireLock将返回Optional.absent(),而AcquireLock将抛出LockNotGrantedException。两种方法都提供了可选参数,可以指定获取锁的超时时间。
以下代码将对DynamoDB实现间隔1秒的轮询,并最多持续5秒钟,以尝试获取锁:
LockItem lock = lockClient.acquireLock("Moe", "Test Data", 1, 5, TimeUnit.SECONDS);
获取锁而不阻塞用户线程
@Test
public void acquireLockNonBlocking() throws LockAlreadyOwnedException {
AcquireLockOptions lockOptions = AcquireLockOptions.builder("partitionKey")
.withShouldSkipBlockingWait(true).build();
LockItem lock = lockClient.acquireLock(lockOptions);
}
上述实现将尝试获取锁定,至少等待租约过期(在我们的情况下为15分钟)。如果该锁已经被其他worker持有。这实质上阻止了该线程处理队列中的其他消息。
因此,Lock Client提供了非阻塞获取锁定的实现。在尝试获取锁时,客户端可以选择设置shouldSkipBlockingWait = true
从而防止用户线程在锁释放前被阻塞。
@Test
public void acquireLockNonBlocking() throws LockAlreadyOwnedException {
AcquireLockOptions lockOptions = AcquireLockOptions.builder("partitionKey")
.withShouldSkipBlockingWait(true).build();
LockItem lock = lockClient.acquireLock(lockOptions);
}
如果该锁不存在,或者该锁已被获取但已过期,则可以成功获取该锁。否则将抛出
LockAlreadyOwnedException异常,通过捕获异常我们可以决定重试锁获取或者退出等待。
读取锁信息
可以在不获取锁的情况下读取锁中的数据,并找出谁拥有锁。就是这样:
LockItem lock = lockClient.getLock("Moe");
Lock Client如何处理时钟偏斜
锁定客户端永远不会在DynamoDB中存储绝对时间-只有相对的“租赁持续时间”时间存储在DynamoDB中。锁过期的方式是,对AcquisitionLock的调用读取当前锁,检查锁的RecordVersionNumber(它是GUID)并启动计时器。如果在租用期限过后该锁仍具有相同的GUID,则客户端将确定该锁已失效并过期。这就是说,即使两台不同的机器对现在的时间意见不一致,它们仍将避免破坏彼此的锁。





