A simple locking implementation built on top of etcd4j.
Currently this library is targetted to Java 8. I do not have plans at the moment to target anyting lower than this.
<dependency>
<groupId>io.johnmurray</groupId>
<artifactId>etcd4j-lock</artifactId>
<version>0.1</version>
</dependency>
Acquire simple lock (no timeout)
try( EtcdLock lock = new EtcdLock(new EtcdClient()) ) {
lock.acquire();
// perform critical operations here
}
// lock released in ARM block (automatically after closing brace)
However this isn't very useful if your lock does not have a name (a unique name will be generated for you). Instead let's assume that we're updating a customer, we can name our lock appropriately.
UUID customerId = ...;
try( EtcdLock lock = new EtcdLock(new EtcdClient()) ) {
lock.withName("update_customer_" + customerId.toString()).acquire();
// perform critical operations here
}
Another useful feature to take advantage of during distributed locks is a lock-lease. If your process dies while you have the lock, you want to make sure that it can be made free after some time limit. Using the same example, let's see how we can define a lease on our lock.
UUID customerId = ...;
try( EtcdLock lock = new EtcdLock(new EtcdClient()) ) {
lock
.withName("update_customer_" + customerId.toString())
.withLockTtl(Duration.ofMinutes(3))
.acquire();
// perform critical operations here
}
Now if our execution time exceeds 3 minutes, we can assume something bad has happened and release the lock. Of course you will also need to make use of proper request fencing when using this feature, but I assume you know distributed locks come with their drawbacks and are ready for this. On that note, you can generate a fencing token that is unique for your lock with:
try( EtcdLock lock = new EtcdLock(new EtcdClient()) ) {
lock
.withName("update_customer_" + customerId.toString())
.withLockTtl(Duration.ofMinutes(3))
.acquire();
EtcdLockToken fencingToken = lock.getLockToken();
// perform critical operations here, pass token to downstream systems
}
The token is a simple structure containing the lock name and the lock index (atomically increasing number). These two
values can be used to properly implement downstream fencing. Note that EtcdLockToken
is returned as an
ImmutableEtcdLockToken
and you are free to share the token value in your program (thread safe) as it cannot be
mutated.
If you are running a long-lived process and need to periodically renew your lease, you can do that as well.
try( EtcdLock lock = new EtcdLock(new EtcdClient()) ) {
lock
.withName("update_customer_" + customerId.toString())
.withLockTtl(Duration.ofMinutes(3))
.acquire();
// perform critical operations here, pass token to downstream systems
// if getting close to timeout, or on some interval, you can renew your lease
lock.renew(Duration.ofMinutes(3))
}
// lock still automatically released irrespective of lease renewal
- Dead simple lock
- Retry lock acquire attempts (
withRetries(N)
) - Acquire attempt timeout (
withAcquireTimeout(Duration)
) - Reader/Writer lock
- Other lock types??