Distributed lock ensures your method cannot be run in parallel from multiple JVMs (cluster of servers, microservices, …). It uses a common store to keep track of used locks and your method needs to acquire one or more locks to run.
By default, locks follow methods lifecycle. They are obtained at the start of the method and released at the end of the method. Manual controlling is supported and explained later in this document.
All locks acquired by lock implementations in this project will expire after 10 seconds, timeout after 1 second if unable to acquire lock and sleep for 50 ms between retries. These options are customizable per annotation.
The project contains several configurations and annotations to help you enable locking and customize it.
To enable locking you must first include @EnableDistributedLock
.
This will import the configuration that will scan all provided Lock
beans and configure the LockAdvice
.
Project provides the following out-of-the-box lock implementations:
-
JDBC
-
Mongo
-
Redis
JDBC locks are provided in the distributed-lock-jdbc
project.
Implementation | Alias | Multiple key support |
---|---|---|
|
|
No |
Include @EnableJdbcDistributedLock
to enable JDBC locks.
This will also include @EnableDistributedLock
for you.
@Configuration
@EnableJdbcDistributedLock
public class LockConfiguration {
}
If you are using Spring Boot and Maven simply add this dependency to your pom.xml
.
<dependency>
<groupId>com.github.alturkovic</groupId>
<artifactId>distributed-lock-jdbc</artifactId>
</dependency>
Note
|
Make sure you create the table and configure the table ID incrementer. |
Example how to create table:
create table lock (
id int not null auto_increment primary key,
key varchar(255) unique,
token varchar(255),
expireAt timestamp,
);
MongoDB locks are provided in the distributed-lock-mongo
project.
Implementation | Alias | Multiple key support |
---|---|---|
|
|
No |
Include @EnableMongoDistributedLock
to enable MongoDB locks.
This will also include @EnableDistributedLock
for you.
@Configuration
@EnableMongoDistributedLock
public class LockConfiguration {
}
If you are using Spring Boot and Maven simply add this dependency to your pom.xml
.
<dependency>
<groupId>com.github.alturkovic</groupId>
<artifactId>distributed-lock-mongo</artifactId>
</dependency>
Redis locks are provided in the distributed-lock-redis
project.
Implementation | Alias | Multiple key support |
---|---|---|
|
|
No |
|
|
Yes |
Include @EnableRedisDistributedLock
to enable Redis locks.
This will also include @EnableDistributedLock
for you.
@Configuration
@EnableRedisDistributedLock
public class LockConfiguration {
}
If you are using Spring Boot and Maven simply add this dependency to your pom.xml
.
<dependency>
<groupId>com.github.alturkovic</groupId>
<artifactId>distributed-lock-redis</artifactId>
</dependency>
To lock your methods you need to first enable locking as described in the previous section.
AOP advice works around the @Locked
annotation. The type
field describes which implementation of the lock to use.
To prevent repeating yourself if you plan on using the same implementation (as most people usually will), I’ve added alias support.
They wrap the @Locked
annotation and define the type used.
Each lock needs to define a SpEL expression used to acquire the lock. To learn more about Spring aliases visit this link.
Sometimes you might want lock to be acquired when calling a specific method and get released only when it expires (throttling).
To acquire a lock that doesn’t get released automatically set manuallyReleased
to true
on @Locked
annotation.
For more grained control (e.g., locking in the middle of the method and releasing later in the code), inject the lock in your service and acquire the lock manually.
@Component
public class Example {
@Qualifier("simpleRedisLock")
private Lock lock;
// other fields...
private void manuallyLocked() {
// code before locking...
final String token = lock.acquire(keys, storeId, expiration, retry, timeout);
// check if you acquired a token
if (StringUtils.isEmpty(token)) {
throw new IllegalStateException("Lock not acquired!");
}
// code after locking...
lock.release(keys, token, storeId);
// code after releasing the lock...
}
}
Locking a method with the name aliased in the document called lock in MongoDB:
@MongoLocked(expression = "'aliased'", typeSpecificStoreId = "lock")
public void runLockedWithMongo() {
// locked code
}
Locking with multiple keys determined in runtime, use SpEL, for an example:
@RedisMultiLocked(expression = "T(com.example.MyUtils).getNamesWithId(#p0)")
public void runLockedWithRedis(final int id) {
// locked code
}
This means that the runLockedWithRedis
method will execute only if all keys evaluated by expression were acquired.
Locking with a custom lock implementation based on value of integer field count
:
@Locked(type = MyCustomLock.class, expression = "getCount", prefix = "using:")
public void runLockedWithMyCustomLock() {
// locked code
}
This is the default key generator the advice uses. If you wish to use your own, don’t use any of the provided configurations,
simply write your own and specify your own KeyGenerator
implementation and pass it to LockAdvice
.
The default key generator has access to the currently executing context, meaning you can access your fields and methods from SpEL.
By default, parameters of the method are accessible from SpEL with #p prefix followed by the parameter index, ie: #p1
is the second parameter.
You can register your own converters to convert your classes to strings that will be used as lock names
using com.github.alturkovic.lock.key.SpelKeyGenerator.registerConverter
method.
Examples provided in com.github.alturkovic.lock.key.SpelKeyGeneratorTest
.
If you want to use custom lock implementations, simply implement Lock
interface and register it in a configuration.
The LockAdvice
will pick it up and register it automatically.
You can also create an alias for your lock so you don’t have to specify @Locked
type field.
The LockAdvice
will recognize your alias automatically.