ShedLock does one and only thing. It makes sure your scheduled tasks ar executed at most once at the same time. It coordinates cluster nodes using shared database. 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. Moreover, if task on one node finishes, it may be executed again (for example due to clock on diferent nodes being out of sync).
Currently, only Spring scheduled tasks coordinated through Mongo, JDBC database or ZooKeeper are supported. More scheduling and coordination mechanisms and expected in the future.
Feedback and pull-requests welcome!
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>0.7.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. Lastly, you can 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.
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 TaskScheduler taskScheduler(LockProvider lockProvider) {
int poolSize = 10;
return SpringLockableTaskSchedulerFactory.newLockableTaskScheduler(poolSize, lockProvider);
}
Or if you already have an instance of ScheduledExecutorService
@Bean
public TaskScheduler taskScheduler(ScheduledExecutorService executorService, LockProvider lockProvider) {
return SpringLockableTaskSchedulerFactory.newLockableTaskScheduler(executorService, lockProvider, Duration.of(10, MINUTES));
}
Import the project
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-mongo</artifactId>
<version>0.7.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
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
Add dependency
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>0.7.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
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>0.7.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.
Import
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-zookeeper-curator</artifactId>
<version>0.7.0</version>
</dependency>
and configure
@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.
If you are using Spring XML config, use this configuration
<!-- Wrap the original scheduler -->
<bean id="scheduler" class="net.javacrumbs.shedlock.spring.SpringLockableTaskSchedulerFactory" factory-method="newLockableTaskScheduler">
<constructor-arg>
<!-- The original scheduler -->
<task:scheduler id="sch" pool-size="10"/>
</constructor-arg>
<constructor-arg ref="lockProvider"/>
</bean>
<!-- Your task(s) without change -->
<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() {
}
- Java 8
- slf4j-api
##Change log
- 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