Перейти к основному содержимому

Введение в кварц

· 8 мин. чтения

1. Обзор

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

Вы можете создавать сложные расписания для выполнения любой работы. Например, задачи, которые выполняются ежедневно, каждую вторую пятницу в 19:30 или только в последний день каждого месяца.

В этой статье мы рассмотрим элементы для создания задания с помощью Quartz API. Для ознакомления с Spring мы рекомендуем Scheduling in Spring with Quartz .

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

Нам нужно добавить следующую зависимость в pom.xml:

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>

Последнюю версию можно найти в репозитории Maven Central .

3. Кварцевый API

Сердцем фреймворка является планировщик . Он отвечает за управление средой выполнения нашего приложения.

Для обеспечения масштабируемости Quartz основан на многопоточной архитектуре. При запуске фреймворк инициализирует набор рабочих потоков , которые используются планировщиком для выполнения заданий .

Вот как фреймворк может запускать множество заданий одновременно. Он также опирается на слабосвязанный набор компонентов управления ThreadPool для управления средой потоков.

Основные интерфейсы API:

  • Scheduler — основной API для взаимодействия с планировщиком фреймворка.
  • Job — интерфейс, который должен быть реализован компонентами, которые мы хотим выполнить.
  • JobDetail — используется для определения экземпляров Job s .
  • Триггер — компонент, определяющий расписание, по которому будет выполняться данное задание .
  • JobBuilder — используется для создания экземпляров JobDetail , которые определяют экземпляры заданий .
  • TriggerBuilder — используется для создания экземпляров Trigger .

Давайте рассмотрим каждый из этих компонентов.

4. Планировщик

Прежде чем мы сможем использовать планировщик , его необходимо создать. Для этого мы можем использовать фабрику SchedulerFactory :

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

Жизненный цикл Scheduler ограничен его созданием через SchedulerFactory и вызовом его метода shutdown() . После создания интерфейс планировщика можно использовать для добавления, удаления и перечисления заданий и триггеров , а также для выполнения других операций, связанных с планированием (таких как приостановка триггера).

Однако планировщик не будет воздействовать ни на какие триггеры, пока он не будет запущен с помощью метода start() :

scheduler.start();

5. Работа

Job — это класс, который реализует интерфейс Job . У него есть только один простой метод:

public class SimpleJob implements Job {
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("This is a quartz job!");
}
}

Когда срабатывает триггер задания , метод execute() вызывается одним из рабочих потоков планировщика.

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

Объект JobDetail создается клиентом Quartz во время добавления задания в планировщик. По сути, это определение экземпляра задания :

JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("myJob", "group1")
.build();

Этот объект также может содержать различные настройки свойств для Job , а также JobDataMap , который можно использовать для хранения информации о состоянии для данного экземпляра нашего класса заданий.

5.1. JobDataMap

JobDataMap используется для хранения любого количества объектов данных, которые мы хотим сделать доступными для экземпляра задания при его выполнении. JobDataMap является реализацией интерфейса Java Map и имеет несколько дополнительных удобных методов для хранения и извлечения данных примитивных типов.

Вот пример помещения данных в JobDataMap при построении JobDetail перед добавлением задания в планировщик:

JobDetail job = newJob(SimpleJob.class)
.withIdentity("myJob", "group1")
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();

А вот пример того, как получить доступ к этим данным во время выполнения задания:

public class SimpleJob implements Job { 
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();

String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");

System.out.println("Job says: " + jobSays + ", and val is: " + myFloatValue);
}
}

В приведенном выше примере будет напечатано «Задание говорит Hello World!, а значение равно 3,141».

Мы также можем добавить в наш класс заданий методы установки, которые соответствуют именам ключей в JobDataMap.

Если мы сделаем это, реализация Quartz JobFactory по умолчанию автоматически вызовет эти установщики при создании экземпляра задания, что избавит от необходимости явно получать значения из карты в нашем методе выполнения.

6. Триггеры

Объекты- триггеры используются для запуска выполнения заданий .

Когда мы хотим запланировать Job , нам нужно создать экземпляр триггера и настроить его свойства, чтобы настроить наши требования к планированию:

Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();

С триггером также может быть связан JobDataMap . Это полезно для передачи в задание параметров , относящихся к выполнению триггера.

Существуют различные типы триггеров для различных потребностей планирования. Каждый из них имеет разные свойства TriggerKey для отслеживания их личности. Однако некоторые другие свойства являются общими для всех типов триггеров:

  • Свойство jobKey указывает идентификатор задания, которое должно выполняться при срабатывании триггера.
  • Свойство startTime указывает, когда расписание триггера впервые вступает в силу. Значение представляет собой объект java.util.Date , определяющий момент времени для данной календарной даты. Для некоторых типов триггеров триггер срабатывает в заданное время запуска. Для других он просто отмечает время начала расписания.
  • Свойство endTime указывает, когда следует отменить расписание триггера.

Quartz поставляется с несколькими различными типами триггеров, но наиболее часто используемыми являются SimpleTrigger и CronTrigger .

6.1. приоритет

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

Например , если одновременно настроено срабатывание десяти триггеров и доступно только четыре рабочих потока, первыми будут выполняться первые четыре триггера с наивысшим приоритетом. Когда мы не устанавливаем приоритет для триггера, он использует приоритет по умолчанию, равный пяти. В качестве приоритета допускается любое целочисленное значение, положительное или отрицательное.

В приведенном ниже примере у нас есть два триггера с разным приоритетом. Если ресурсов недостаточно для срабатывания всех триггеров одновременно, triggerA сработает первым:

Trigger triggerA = TriggerBuilder.newTrigger()
.withIdentity("triggerA", "group1")
.startNow()
.withPriority(15)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();

Trigger triggerB = TriggerBuilder.newTrigger()
.withIdentity("triggerB", "group1")
.startNow()
.withPriority(10)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(20)
.repeatForever())
.build();

6.2. Инструкции по осечкам

Осечка возникает, если постоянный триггер пропускает время срабатывания из-за закрытия планировщика или в случае отсутствия доступных потоков в пуле потоков Quartz.

Для разных типов триггеров доступны разные инструкции по осечкам. По умолчанию они используют интеллектуальную инструкцию политики. Когда планировщик запускается, он ищет любые постоянные триггеры, которые не сработали. После этого он обновляет каждый из них на основе индивидуально настроенных инструкций по осечкам.

Давайте посмотрим на примеры ниже:

Trigger misFiredTriggerA = TriggerBuilder.newTrigger()
.startAt(DateUtils.addSeconds(new Date(), -10))
.build();

Trigger misFiredTriggerB = TriggerBuilder.newTrigger()
.startAt(DateUtils.addSeconds(new Date(), -10))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow())
.build();

Мы запланировали запуск триггера на 10 секунд назад (поэтому к моменту его создания он запаздывает на 10 секунд) для имитации осечки, например, из-за того, что планировщик не работает или не имеет достаточного количества доступных рабочих потоков. Конечно, в реальном сценарии мы бы никогда не запланировали такие триггеры.

В первом триггере ( misFiredTriggerA ) инструкции по обработке осечек не заданы. Следовательно, в этом случае используется вызываемая интеллектуальная политика , которая называется: withMisfireHandlingInstructionFireNow(). Это означает, что задание выполняется сразу после того, как планировщик обнаружит осечку.

Второй триггер явно определяет, какое поведение мы ожидаем при возникновении осечки. В этом примере это просто одна и та же умная политика.

6.3. Простой триггер

SimpleTrigger используется для сценариев, в которых нам нужно выполнить задание в определенный момент времени. Это может быть либо ровно один раз, либо повторно через определенные промежутки времени.

Примером может быть запуск выполнения задания ровно в 00:20:00 13 января 2018 года. Точно так же мы можем начать в это время, а затем еще пять раз каждые десять секунд.

В приведенном ниже коде дата myStartTime была определена ранее и используется для создания триггера для одной конкретной метки времени :

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime)
.forJob("job1", "group1")
.build();

Далее строим триггер на конкретный момент времени, затем повторяем каждые десять секунд десять раз:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
.startAt(myStartTime)
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10))
.forJob("job1")
.build();

6.4. CronTrigger

CronTrigger используется, когда нам нужны расписания, основанные на операторах, подобных календарю . Например, мы можем указать расписание запуска, например, каждую пятницу в полдень или каждый будний день в 9:30 утра .

Cron-Expressions используются для настройки экземпляров CronTrigger . Эти выражения состоят из строк , состоящих из семи подвыражений. Подробнее о Cron-Expressions можно прочитать здесь .

В приведенном ниже примере мы создаем триггер, который срабатывает каждую вторую минуту с 8:00 до 17:00 каждый день:

CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();

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

В этой статье мы показали, как создать планировщик для запуска задания . Мы также видели некоторые из наиболее распространенных параметров триггера: SimpleTrigger и CronTrigger .

Quartz можно использовать для создания простых или сложных расписаний для выполнения десятков, сотен или даже большего количества заданий. Более подробную информацию о фреймворке можно найти на основном сайте .

Исходный код примеров можно найти на GitHub .