«1. Обзор

Spring предоставляет простой в реализации API для планирования заданий. Он отлично работает, пока мы не развернем несколько экземпляров нашего приложения. Spring по умолчанию не может обрабатывать синхронизацию планировщика по нескольким экземплярам — вместо этого он выполняет задания одновременно на каждом узле.

В этом кратком руководстве мы рассмотрим ShedLock — библиотеку Java, которая гарантирует, что наши запланированные задачи запускаются только один раз в одно и то же время, и является альтернативой Quartz.

2. Зависимости Maven

Чтобы использовать ShedLock с Spring, нам нужно добавить зависимость shedlock-spring:

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

3. Конфигурация

Обратите внимание, что ShedLock работает только в средах с общей базой данных объявление правильного LockProvider. Он создает таблицу или документ в базе данных, где хранится информация о текущих блокировках.

В настоящее время ShedLock поддерживает Mongo, Redis, Hazelcast, ZooKeeper и все, что имеет драйвер JDBC.

В этом примере мы будем использовать базу данных H2 в памяти. Чтобы заставить его работать, нам нужно предоставить базу данных H2 и JDBC-зависимость ShedLock:

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
     <groupId>com.h2database</groupId>
     <artifactId>h2</artifactId>
     <version>1.4.200</version>
</dependency>

Затем нам нужно создать таблицу базы данных для ShedLock, чтобы хранить информацию о блокировках планировщика:

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

Мы должны объявите источник данных в файле свойств нашего приложения Spring Boot, чтобы bean-компонент DataSource мог быть Autowired. В этом примере мы используем application.yml для определения источника данных базы данных H2:

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:shedlock_DB;INIT=CREATE SCHEMA IF NOT EXISTS shedlock;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:

Давайте настроим LockProvider с приведенной выше конфигурацией источника данных. Spring может сделать это довольно просто:

@Configuration
public class SchedulerConfiguration {
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

Еще одно требование к конфигурации, которое мы должны предоставить, — это аннотации @EnableScheduling и @EnableSchedulerLock в нашем классе конфигурации Spring:

@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class Application {

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

Параметр defaultLockAtMostFor указывает количество времени по умолчанию, в течение которого блокировка должна быть сохранена на случай, если исполняющий узел умрет. Он использует формат продолжительности ISO8601.

В следующем разделе мы увидим, как переопределить это значение по умолчанию.

4. Создание задач

Чтобы создать запланированную задачу, обрабатываемую ShedLock, мы просто добавляем аннотации @Scheduled и @SchedulerLock к методу:

@Component
class BaeldungTaskScheduler {

    @Scheduled(cron = "0 0/15 * * * ?")
    @SchedulerLock(name = "TaskScheduler_scheduledTask", 
      lockAtLeastForString = "PT5M", lockAtMostForString = "PT14M")
    public void scheduledTask() {
        // ...
    }
}

Сначала давайте посмотрим на @Scheduled. Он поддерживает формат cron, где это выражение означает «каждые 15 минут».

Далее, взглянем на @SchedulerLock, параметр имени должен быть уникальным, и для этого обычно достаточно ClassName_methodName. Мы не хотим, чтобы одновременно выполнялось более одного запуска этого метода, и ShedLock использует для этого уникальное имя.

Мы также добавили несколько необязательных параметров.

Во-первых, мы добавили lockAtLeastForString, чтобы мы могли установить некоторое расстояние между вызовами методов. Использование «PT5M» означает, что этот метод будет удерживать блокировку как минимум на 5 минут. Другими словами, это означает, что этот метод может запускаться ShedLock не чаще, чем каждые пять минут.

Затем мы добавили lockAtMostForString, чтобы указать, как долго должна сохраняться блокировка в случае выхода из строя исполняющего узла. Использование «PT14M» означает, что он будет заблокирован не более чем на 14 минут.

В обычных ситуациях ShedLock снимает блокировку сразу после завершения задачи. Теперь нам не нужно было делать это, потому что в @EnableSchedulerLock есть значение по умолчанию, но мы решили переопределить его здесь.

5. Заключение

В этой статье мы узнали, как создавать и синхронизировать запланированные задачи с помощью ShedLock.

Как всегда, весь исходный код доступен на GitHub.