If you’re building applications that run on multiple instances, you’ll need a reliable way to ensure your scheduled tasks don’t execute simultaneously across different nodes. 

This comprehensive guide will walk you through implementing ShedLock in your Spring Boot application, demonstrating how to prevent duplicate scheduled task execution in distributed environments

You’ll learn everything from basic setup to advanced configurations, using Spring Boot and ShedLock.

Understanding Distributed Locking

Distributed locking is a mechanism you’ll need when running scheduled tasks across multiple application instances. 

When you deploy your Spring Boot application on several servers, you need to ensure that scheduled tasks run only once at a given time. 

This is where ShedLock comes in, providing a robust distributed lock that prevents your scheduled tasks from executing simultaneously across different nodes.

ShedLock makes sure that your scheduled tasks are executed at most once at the same time.

Configuring Maven/Gradle Dependencies

If you’re using Maven for your project, you’ll need to add ShedLock dependencies to your pom.xml or build.gradle file. 

The core dependency is shedlock-spring and a storage provider of your choice.

Setup your Maven dependencies with the following configuration:

// MAVEN
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>6.3.0</version>
</dependency>

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>6.3.0</version>
</dependency>

// GRADLE
implementation 'net.javacrumbs.shedlock:shedlock-spring:6.3.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:6.3.0'

With ShedLock, you need to configure a lock provider in your Spring Boot application. 

The most common approach is using a database-based lock provider.

Setting Up Database Connectivity

If you’re using a database-based lock provider, you’ll need to configure your database connection in application.properties or application.yml file.

spring:
  datasource:
    url:jdbc:postgresql://localhost:5432/your_database 
    username:your_username 
    password: your_password 

Also, you need to have corresponding database related dependencies in your pom.xml or build.gradle.

Refer how to use spring data jpa for database related operations.

You also need to create a table in your application database for which the following database respective queries can be used.

# MySQL, MariaDB
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, 
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 
    locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# Postgres
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, 
    lock_until TIMESTAMP NOT NULL,
    locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, 
    PRIMARY KEY (name));

# Oracle
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, 
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, 
    PRIMARY KEY (name));

# MS SQL
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, 
    lock_until datetime2 NOT NULL,
    locked_at datetime2 NOT NULL, locked_by VARCHAR(255) NOT NULL, 
    PRIMARY KEY (name));

# DB2
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, 
    lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, 
    locked_by VARCHAR(255) NOT NULL);

This is required since ShedLock uses database to manage locks.

ShedLock Architecture and Components

Fundamentals of ShedLock revolve around three main components: 

  1. LockProvider interface, which manages lock acquisition and release; 
  2. @SchedulerLock annotation, which you’ll use to configure lock parameters; and 
  3. LockConfiguration class, which holds lock-specific settings. 

You can choose from various lock providers including JDBC, Redis, MongoDB, and others to store lock information.

Defining Scheduled Tasks

To implement scheduled tasks in your Spring Boot application, you’ll need to define methods annotated with @Scheduled

These tasks can be configured with fixed rates, delays, or cron expressions. 
You’ll start by enabling scheduling in your application using @EnableScheduling annotation at the configuration class level. 

This setup forms the foundation for implementing distributed locks with ShedLock.

Implementing ShedLock on Scheduled Methods

Any scheduled task that requires distributed locking can be secured using the @SchedulerLock annotation. 

You’ll need to specify a unique name for each lock and set appropriate lock parameters like lockAtMostFor and lockAtLeastFor to control lock duration and prevent task overlap.

A typical implementation includes both @Scheduled and @SchedulerLock annotations on your method. 

The lockAtMostFor parameter ensures the lock is automatically released after the specified duration, preventing deadlocks if your application crashes. 

You can set these values based on your task’s expected execution time.

@Scheduled(cron = "0 */15 * * * *") // runs every 15 mins
@SchedulerLock(name = "TaskName_scheduledTask", 
               lockAtMostFor = "14m", 
               lockAtLeastFor = "5m") 
public void scheduledTask() { 
  // Your task implementation 
}

In this example,

The @Scheduled annotation is part of Spring Framework’s task scheduling capabilities. 

It allows you to execute methods at specified intervals without manual intervention.

cron = “0 */15 * * * *” defines when the task will run using a cron expression
This specific expression translates to: “Run at 0 seconds, every 15 minutes, every hour, every day, every month, every day of the week”

@Scheduled annotation is perfect for recurring tasks like data cleanup, report generation, or periodic data processing.

The @SchedulerLock annotation comes from the ShedLock library, which provides distributed locking for scheduled tasks. 

Key parameters

name = “TaskName_scheduledTask”
– Provides a unique identifier for this scheduled task
– Must be unique across your entire application to prevent lock conflicts

lockAtMostFor = “14m”
– Sets the maximum duration the lock will be held
– After 14 minutes, the lock will be automatically released even if the task is still running
– This prevents deadlocks if a task instance crashes without releasing the lock
– Set to slightly less than the scheduling interval (15 minutes) to prevent overlap

lockAtLeastFor = “5m”
– Defines the minimum time the lock will be held
– Ensures the lock won’t be released for at least 5 minutes, even if the task completes earlier
– Prevents task execution by other nodes if the task completes very quickly
– Useful for preventing double execution in highly distributed environments

Remember that only methods that have @SchedulerLock annotation are locked and will be the candidates for being run on a single application instance.

Setting Up a Lock Provider

With ShedLock, you need to configure a lock provider in your Spring Boot application. 

The most common approach is using a database-based lock provider:

@Configuration 
@EnableSchedulerLock(defaultLockAtMostFor = "10m") 
public class ShedLockConfig { 
  @Bean 
  public LockProvider lockProvider(DataSource dataSource) { 
    return new JdbcTemplateLockProvider(dataSource); 
  } 
}

Configuration of the lock provider requires careful consideration of your infrastructure. 

You can choose from various providers including JDBC, MongoDB, Redis, or others based on your application’s architecture. 

Each provider has specific configuration requirements and performance characteristics.

Customizing Lock Provider

Above configuration creates a lock provider with default configuration.

But, you can also customize lock provider configuration such as the datasource, name of database table, names of columns etc., as shown below.

@Bean 
public LockProvider lockProvider(DataSource dataSource) { 
  return new JdbcTemplateLockProvider(
       JdbcTemplateLockProvider.
       Configuration.
       builder().
       withJdbcTemplate(new JdbcTemplate(dataSource)).
       withTableName("shedlock").
       withTimeZone(TimeZone.getTimeZone("UTC")).
       withColumnNames(
          new ColumnNames("name", "lock_until", "locked_at", "locked_by")).
       build() ); 
}

Benefits of Using ShedLock in Spring Boot

ShedLock offers you a reliable solution for handling distributed scheduled tasks in your Spring Boot applications. 

You get 

  1. automatic lock management, 
  2. support for various storage providers, and 
  3. seamless integration with Spring’s scheduling mechanism. 

With minimal configuration, you can prevent task overlapping and ensure consistent execution across your distributed system.

Testing ShedLock Implementation

Testing of ShedLock can be performed by running multiple instances of your application simultaneously. 

You’ll observe that only one instance executes the scheduled task while others wait for lock release. 

This behavior ensures task execution isolation across your distributed system.

Methods for testing lock behavior include monitoring lock tables in your database, checking application logs for lock acquisition patterns, and verifying task execution timestamps across different application instances. 

Integrating ShedLock with Distributed Systems

Lock management in distributed environments requires careful coordination. 

You can integrate ShedLock with various distributed storage systems like Redis or MongoDB to ensure consistent locking across your entire application cluster.

@Configuration 
public class ShedLockConfig { 
  @Bean 
  public LockProvider lockProvider(MongoClient mongo) { 
    return new MongoLockProvider(
        mongo.getDatabase("scheduler")); 
  } 
}

You can also implement custom health checks and monitoring to detect and respond to lock-related issues across your distributed infrastructure as shown below

@Configuration 
public class ShedLockConfig { 

  @Bean 
  public HealthIndicator healthIndicator(LockProvider lockProvider) { 
    return new ShedLockHealthIndicator(lockProvider); 
  } 
}

Monitoring Lock Performance

The most effective way to monitor ShedLock performance is by implementing metrics collection and alerting systems. 

You can use Spring Boot Actuator to expose lock-related metrics and integrate with monitoring tools like Prometheus and Grafana.

Issues with lock performance can be identified early by setting up proper monitoring dashboards. 

You should track metrics such as lock acquisition time, lock duration, and failed attempts. 

Here’s an example of implementing custom metrics:

@Component 
public class ShedLockMetrics { 
  private final MeterRegistry registry; 
 
  public ShedLockMetrics(MeterRegistry registry) { 
    this.registry = registry; 
  } 

  public void recordLockAcquisition(String taskName, long duration) { 
    registry.
    timer("shedlock.acquisition", "task", taskName).
    record(duration, TimeUnit.MILLISECONDS); 
  } 
}

Summing up

You’ve learned how to effectively implement distributed task scheduling using Spring Boot and ShedLock. 

Your applications can now avoid task overlapping by implementing locks with Spring Boot and ShedLock library.

You can ensure your scheduled tasks run efficiently across multiple instances. 
This knowledge empowers you to build more reliable and scalable distributed systems.

Categorized in:

Uncategorized,