preface

ShedLock ensures that you plan tasks to be executed at most once simultaneously. If a task is executing on a node, it acquires a lock that prevents the same task from being executed from another node (or thread). Note that if a task has already been executed on one node, execution on the other nodes will not wait, but will simply be skipped. ShedLock uses external storage (such as Mongo, JDBC databases, Redis, Hazelcast, ZooKeeper, or other repositories) for coordination.

Note: the website states that it is only a distributed lock, not a scheduler.


What is ShedLock?

Official address: github.com/lukas-kreca…

The following is the ShedLock lock provider, which implements locks through external storage. As you can see from the following figure, the external storage integration library is quite rich:

In this tutorial we use the ShedLock lock as an example based on the JdbcTemplate store.

Two, landing implementation

1.1 Importing dependency packages

Dependencies required by Shedlock:

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>4.23.0</version>
</dependency>
 <! -- Each external storage instance requires a different dependency package, here is JDBC -->
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>4.23.0</version>
</dependency>
Copy the code

JdbcTemplate dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
Copy the code

Web Project dependency packages:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code

1.2 Configuring database Connection Information

server:
  port: 9999
spring:
  datasource:
    url: JDBC: mysql: / / 192.168.31.158:3306 / testjdbc? useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.mysql.cj.jdbc.MysqlDataSource
Copy the code

1.3 Creating a Mysql table

# 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);
Copy the code

1.4 configuration LockProvider

/ * * *@description: Shedlock integrates the Jdbc configuration class *@author: DT
 * @date: 2021/5/22 0:07
 * @version: v1.0 * /
// Identifies the class as a configuration class
@Configuration
// // Start timer
@EnableScheduling
// Enable a timed task lock. Specify a default lock duration of 30 seconds
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ShedlockJdbcConfig {

    /** * Configures the lock provider */
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                        .withJdbcTemplate(newJdbcTemplate(dataSource)) .usingDbTime() .build() ); }}Copy the code

1.5 Creating a Scheduled Job

/ * * *@description: Tasks for enabling distributed locking *@author: DT
 * @date: 2021/5/22 0:23
 * @version: v1.0 * /
@Component
@CommonsLog
public class TimeTaskJob {

    private static Integer count = 1;

    // @schedulerLock is used to ensure that the method of the current scheduled task gets the lock while it executes, ignoring the execution of other similar tasks
    // Name must be specified. ShedLock uses this name for the same task
    // name: the name of the scheduled task, which is the primary key in the database (name)
    // lockAtMostFor: The maximum lock time is in milliseconds
    // lockAtLeastFor: The minimum lock time is in milliseconds

    /** * Task 1 executes every 5 seconds * lockAtLeastFor: Although scheduled tasks are executed every five seconds, distributed locks are defined as follows: Each task must be locked for 20 seconds, which is the minimum time for holding the lock. The lock must be released after 20 seconds, and the task will not run more than once within 20 seconds. * lockAtMostFor: The maximum holding time of a lock is 30 seconds. It is used to prevent the node executing a task from hanging (even if the node hangs, the lock will be released after 30 seconds). It is generally set to a value that is significantly longer than the maximum execution time of a task. If the task takes longer than this value (that is, the task is not completed in 30 seconds), the task may be repeated. * /
    @Scheduled(cron = "0/5 * * * * ? ")
    @SchedulerLock(name = "testJob1",lockAtLeastFor = "20000", lockAtMostFor = "30000")
    public void scheduledTask1(a) {
        log.info(Thread.currentThread().getName() + "->>> Task 1: + (count++) + "Time");
    }

    /** * Task 2 is executed every 5 seconds */
    @Scheduled(cron = "0/5 * * * * ? ")
    @SchedulerLock(name = "testJob2")
    public void scheduledTask2(a) {
        log.info(Thread.currentThread().getName() + "->>> Task 2: + (count++) + "Time"); }}Copy the code

1.6 Main boot Class

@SpringBootApplication
public class JdbcApplication {

    public static void main(String[] args) { SpringApplication.run(JdbcApplication.class, args); }}Copy the code

1.7 Analyzing the Execution Success

You can see that the two tasks are executed alternately, and that our count increments by 1 each time in an orderly fashion. But it should be noted that ShedLock is single-threaded.

1.7 Changing table Names

ShedLock supports relational databases, using mysql as an example, configure mysql and table names; ShedLock The default table name is shedLock and you can set custom table names.

conclusion

Core idea: By recording and locking a table in a common database, only the first node that executes a scheduled task and successfully writes corresponding records in the database table at the same time can successfully execute the task while other nodes directly skip this task.