I talked about how to create a scheduled job in this post. Most of us probably faced with a use case to make only one specific cron job to run at a time in a distributed environment. For example; we don't want to run multiple cron jobs to send same email to customers or charging them multiple times. To avoid this we have to find a way to make a cron job run only in one instance. For this we will walk through how to use Shedlock which provides a distributed lock mechanism for cron jobs. Well for this we also need to have a database so I will use MySQL in this example to run locally.
First step is creating a table for locks to be used by Shedlock. I assume you already have a MySQL running locally (Or any other JDBC compliant database like MariaDB, Postgres etc). Then we have to a table;
Later on, we have to add one annotation and a bean producer for LockProvider for enabling Shedlock. So, our Application class will look like;
EnableSchedulerLock annotation enables Shedlock in Spring context so that we can use it for our jobs. defaultLockAtMostFor attribute is required but it can be overridden by individual tasks.
We need to create a Bean for LockProvider which will be used by Shedlock to update lock table we created.
Running a Job with Shedlock
All jobs which need distributed lock should be annotated with @SchedulerLock annotation, otherwise, jobs will run in parallel. There are three main parameters to configure.
name - Every job has to have a unique name. Remember that name field from shedlock table was the primary key
lockAtLeastFor - This attribute is for ensuring the current job will hold the lock at least given amount of time. For example; if we configure it to 20 seconds then the lock won't released even though job finished before 20 seconds.
lockAtMostFor - This attribute is for making sure lock is released in case of executing instance dies. As it is suggested from the documentation itself it will be better to set this attribute larger than the maximum estimated execution time. If a task takes longer than this value than an unexpected behaviour might happen because some other instance will also run the same job since lock will be released.
I will create a job called AwesomeJob with lockAtLeastFor attribute set to 15 seconds and lockAtMostFor attribute set to 20 seconds. The actual time it will take to run will be just 5 seconds and it will run at every minute.
Now I configured this Spring application to run as two different instances via Docker with a MySQL instance so that we can verify one of the instance will hold the lock and other instance won't run any job. Here is the docker-compose.yml file I used to spin up docker network.
The whole project with docker files can be found here
So we have 2 spring application with container Ids of 938e2903934b and f1177afa6b27. One of them will hold the lock and rum the job. I started two applications at the same time and shared the logs below;
Now as you can see one of them started to run the job. Lets see the table we created for lock information.
As we can see that lock AwesomeJob held by instance f1177afa6b27 for 15 seconds. After some time, instance 938e2903934b will hold the lock and run the job.
I hope this illustration was enough and helpful to show how to use Shedlock for distributed lock mechanism in Spring Applications. Additionally, you can find the whole Dockerized project here