ShedLock does one and only one thing. It makes sure your scheduled tasks are executed at most once at the same time. If a task is being executed on one node, it acquires a lock which prevents execution of the same task from another node (or thread). Please note, that if one task is already being executed on one node, execution on other nodes does not wait, it is simply skipped.
Currently, Spring scheduled tasks coordinated through Mongo, JDBC database, Redis, Hazelcast or ZooKeeper are supported. More scheduling and coordination mechanisms and expected in the future.
Feedback and pull-requests welcome!
Please note that ShedLock is not and will never be full-fledged scheduler, it's just a lock. If you need a distributed scheduler, please use another project. ShedLock is designed to be used in situations where you have scheduled tasks that are not ready to be executed in parallel, but can be safely executed repeatedly. For example if the task is fetching records from a database, processing them and marking them as processed at the end without using any transaction. In such case ShedLock may be right for you.
- Java 8
- slf4j-api
- Spring Framework (optional)
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>1.1.0</version>
</dependency>
import net.javacrumbs.shedlock.core.SchedulerLock;
...
@Scheduled(...)
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
// do something
}
The @SchedulerLock
annotation has several purposes. First of all, only annotated methods are locked, the library ignores
all other scheduled tasks. You also have to specify the name for the lock. Only one tasks with the same name can be executed
at the same time.
You can also set lockAtMostFor
attribute which specifies how long the lock should be kept in case the
executing node dies. This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes.
Lastly, you can set lockAtLeastFor
attribute which specifies minimum amount of time for which the lock should be kept.
Its main purpose is to prevent execution from multiple nodes in case of really short tasks and clock difference between the nodes.
Let's say you have a task which you execute every 15 minutes and which usually takes few minutes to run. Moreover, you want to execute it at most once per 15 minutes. In such case, you can configure it like this
import net.javacrumbs.shedlock.core.SchedulerLock;
...
private static final int FOURTEEN_MIN = 14 * 60 * 1000;
...
@Scheduled(cron = "0 */15 * * * *")
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = FOURTEEN_MIN, lockAtLeastFor = FOURTEEN_MIN)
public void scheduledTask() {
// do something
}
By setting lockAtMostFor
we make sure that the lock is released even if the node dies and by setting lockAtLeastFor
we make sure it's not executed more than once in fifteen minutes.
Please note that lockAtMostFor
is just a safety net for a case that the node executing the task dies, so set it to
a time that is significantly larger than maximum estimated execution time. If the task takes longer than lockAtMostFor
,
it will be executed again.
Now we need to integrate the library into Spring. It's done by wrapping standard Spring task scheduler.
import net.javacrumbs.shedlock.spring.SpringLockableTaskSchedulerFactory;
...
@Bean
public ScheduledLockConfiguration taskScheduler(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
.withPoolSize(10)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
Or if you already have an instance of ScheduledExecutorService
@Bean
public ScheduledLockConfiguration taskScheduler(ScheduledExecutorService executorService, LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
.withExecutorService(executorService)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
There are several implementations of LockProvider.
Import the project
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-mongo</artifactId>
<version>1.1.0</version>
</dependency>
Configure:
import net.javacrumbs.shedlock.provider.mongo.MongoLockProvider;
...
@Bean
public LockProvider lockProvider(MongoClient mongo) {
return new MongoLockProvider(mongo, "databaseName");
}
Please note that MongoDB integration requires Mongo >= 2.4 and mongo-java-driver >= 3.4.0
There are two lock providers based on JDBC. JdbcTemplateLockProvider which uses Spring's JDBC template, supports transactions etc, and plain JdbcLockProvider which asseses JDBC directly. They should work more or less the same, if in doubt, use JdbcTemplateLockProvider.
First, create the table
CREATE TABLE shedlock(
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
script for MS SQL is here and for Oracle here
Add dependency
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>1.1.0</version>
</dependency>
Configure:
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
Tested with MySql, Postgres and HSQLDB, should work on all other JDBC compliant databases.
For those who do not want to use jdbc-template, there is plain JDBC lock provider. Just import
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc</artifactId>
<version>1.1.0</version>
</dependency>
and configure
import net.javacrumbs.shedlock.provider.jdbc.JdbcLockProvider;
...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcLockProvider(dataSource);
}
the rest is the same as with JdbcTemplate lock provider.
Do not manually delete lock row or document from DB table or Mongo collection. ShedLock has an in-memory cache of existing locks
so the row will NOT be automatically recreated until application restart. If you need to, you can edit the row/document, risking only
that multiple locks will be held. Since 1.0.0 you can clean the cache by calling clearCache()
on LockProvider.
Import
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-zookeeper-curator</artifactId>
<version>1.1.0</version>
</dependency>
and configure
import net.javacrumbs.shedlock.provider.zookeeper.curator.ZookeeperCuratorLockProvider;
...
@Bean
public LockProvider lockProvider(org.apache.curator.framework.CuratorFramework client) {
return new ZookeeperCuratorLockProvider(client);
}
By default, ephemeral nodes for locks will be created under /shedlock
node.
Import
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>1.1.0</version>
</dependency>
and configure
import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
import org.springframework.data.redis.connection.RedisConnectionFactory;
...
@Bean
public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {
return new RedisLockProvider(connectionFactory, ENV);
}
If you have dependency on spring-data-redis 2 use ShedLock 1.x.x, if you have dependency on spring-data-redis 1 use ShedLock 0.x.x.
Import
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-jedis</artifactId>
<version>1.1.0</version>
</dependency>
and configure
import net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvider;
...
@Bean
public LockProvider lockProvider(JedisPool jedisPool) {
return new JedisLockProvider(jedisPool, ENV);
}
Import the project
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-hazelcast</artifactId>
<version>1.1.0/version>
</dependency>
Configure:
import net.javacrumbs.shedlock.provider.hazelcast.HazelcastLockProvider;
...
@Bean
public HazelcastLockProvider lockProvider(HazelcastInstance hazelcastInstance) {
return new HazelcastLockProvider(hazelcastInstance);
}
If you are using Spring XML config, use this configuration
<!-- lock provider of your choice (jdbc/zookeeper/mongo/whatever) -->
<bean id="lockProvider" class="net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="scheduler" class="net.javacrumbs.shedlock.spring.SpringLockableTaskSchedulerFactoryBean">
<constructor-arg>
<task:scheduler id="sch" pool-size="10"/>
</constructor-arg>
<constructor-arg ref="lockProvider"/>
<constructor-arg name="defaultLockAtMostFor">
<bean class="java.time.Duration" factory-method="ofMinutes">
<constructor-arg value="10"/>
</bean>
</constructor-arg>
</bean>
<!-- Your task(s) without change (or annotated with @Scheduled)-->
<task:scheduled-tasks scheduler="scheduler">
<task:scheduled ref="task" method="run" fixed-delay="1" fixed-rate="1"/>
</task:scheduled-tasks>
Annotate scheduler method(s)
@SchedulerLock(name = "taskName")
public void run() {
}
It is possible to use ShedLock without Spring
LockingTaskExecutor executor = new DefaultLockingTaskExecutor(lockProvider);
...
Instant lockAtMostUntil = Instant.now().plusSeconds(600);
executor.executeWithLock(runnable, new LockConfiguration("lockName", lockAtMostUntil));
Version 1.x.x is compiled and tested with Spring 5 and Spring Data 2. It should be safe to use ShedLock 1.x.x with Spring 4 if you are not using Spring Redis lock provider which introduced incompatibility. In other words
- If you have dependency on spring-data-redis 2 - use ShedLock 1.x.x
- If you have dependency on spring-data-redis 1 - use ShedLock 0.x.x
- In all other cases, you can use both versions, prefereably 1.x.x
- Upgraded dependencies to Spring 5 and Spring Data 2
- Removed deprecated net.javacrumbs.shedlock.provider.jedis.JedisLockProvider (use net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvide instead)
- Removed deprecated SpringLockableTaskSchedulerFactory (use ScheduledLockConfigurationBuilder instead)
- ablility to clean lock cache
- shedlock-provider-redis-spring made compatible with spring-data-redis 1.x.x
- Added shedlock-provider-redis-spring (thanks to @siposr)
- shedlock-provider-jedis moved to shedlock-provider-redis-jedis
- Support for SPEL in lock name annotation
- Automatically closing TaskExecutor on Spring shutdown
- Removed spring-test from shedlock-spring compile time dependencies
- Added Automatic-Module-Names
- Hazelcast works with remote cluster
- Fixed ScheduledLockConfigurationBuilder interfaces #32
- Hazelcast code refactoring
- Support for Hazelcast (thanks to @peyo)
- Jedis constructor made more generic (thanks to @mgrzeszczak)
- Support for property placeholders in annotation lockAtMostForString/lockAtLeastForString
- Support for composed annotations
- ScheduledLockConfigurationBuilder introduced (deprecating SpringLockableTaskSchedulerFactory)
- Support for Redis (thanks to @clamey)
- Checking that lockAtMostFor is in the future
- Checking that lockAtMostFor is larger than lockAtLeastFor
- jdbc-template-provider does not participate in task transaction
- Support for @SchedulerLock annotations on proxied classes
- LockableTaskScheduler made AutoClosable so it's closed upon Spring shutdown
- Support for lockAtLeastFor
- Possible to configure defaultLockFor time so it does not have to be repeated in every annotation
- ZooKeeper nodes created under /shedlock by default
- JdbcLockProvider insert does not fail on DataIntegrityViolationException
- Extracted LockingTaskExecutor
- LockManager.executeIfNotLocked renamed to executeWithLock
- Default table name in JDBC lock providers
@ShedlulerLock.name
made obligatory@ShedlulerLock.lockForMillis
renamed to lockAtMostFor- Adding plain JDBC LockProvider
- Adding ZooKeepr LockProvider