Статьи

Кварцевый планировщик пояснения инструкции по пропуску

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

  • все рабочие потоки были заняты выполнением других заданий (возможно, с более высоким приоритетом)
  • сам планировщик не работал
  • задание было запланировано с начальным временем в прошлом (вероятно, ошибка кодирования)

Вы можете увеличить количество рабочих потоков, просто настроив org.quartz.threadPool.threadCount в quartz.properties (по умолчанию 10). Но вы не можете ничего сделать, когда все приложение / сервер / планировщик не работает. Ситуация, когда Кварц был неспособен к срабатыванию данного триггера, называется пропуском огня. Знаете ли вы, что делает Кварц, когда это происходит? Оказывается, существуют различные стратегии (так называемые инструкции по пропускам зажигания ), которые может использовать Quartz, а также есть некоторые настройки по умолчанию, если вы не задумывались об этом. Но для того, чтобы сделать ваше приложение надежным и предсказуемым (особенно при большой нагрузке или обслуживании), вы должны действительно убедиться, что ваши триггеры и задания сконфигурированы четко.

Существуют различные параметры конфигурации (доступные инструкции по пропускам зажигания ) в зависимости от выбранного триггера. Кроме того, Quartz ведет себя по-разному в зависимости от настройки триггера (так называемая интеллектуальная политика ). Хотя инструкции по пропускам зажигания описаны в документации, мне было трудно понять, что они на самом деле означают. Итак, я создал эту небольшую сводную статью.

Прежде чем углубиться в детали, необходимо описать еще один вариант конфигурации. Это org.quartz.jobStore.misfireThreshold (в миллисекундах), по умолчанию 60000 (минута). Он определяет, как поздно триггер должен считаться пропущенным . При настройке по умолчанию, если триггер предполагалось сработать 30 секунд назад, Quartz с радостью просто запустит его. Такая задержка не считается пропуском зажигания. Однако, если триггер обнаружен через 61 секунду после запланированного времени — специальный поток обработчика пропуска зажигания позаботится об этом, подчиняясь инструкции пропуска зажигания. В целях тестирования мы установим этот параметр на 1000 (1 секунда), чтобы мы могли быстро проверить пропуски зажигания.

Простой триггер без повторения

В нашем первом примере мы увидим, как сбои обрабатываются простыми триггерами, запланированными для запуска только один раз:

1
2
3
val trigger = newTrigger().
        startAt(DateUtils.addSeconds(new Date(), -10)).
        build()

Тот же триггер, но с явно установленным обработчиком инструкций пропуска зажигания:

1
2
3
4
5
6
7
val trigger = newTrigger().
        startAt(DateUtils.addSeconds(new Date(), -10)).
        withSchedule(
            simpleSchedule().
                withMisfireHandlingInstructionFireNow()  //MISFIRE_INSTRUCTION_FIRE_NOW
            ).
        build()

В целях тестирования я просто планирую запуск триггера 10 секунд назад (так что к моменту создания он опаздывает на 10 секунд). В реальном мире вы обычно никогда не запланировали бы триггеры подобным образом. Вместо этого представьте, что триггер был установлен правильно, но к тому времени, когда он был запланирован, планировщик не работал или не имел свободных рабочих потоков. Тем не менее, как Кварц справится с этой необычной ситуацией? В первом фрагменте кода выше инструкция по обработке пропусков зажигания не установлена ​​(в этом случае используется так называемая умная политика ). Второй фрагмент кода явно определяет, какое поведение мы ожидаем, когда происходит сбой. Смотрите таблицу:

Простой триггер повторяется фиксированное количество раз

Этот сценарий намного сложнее. Представьте, что мы запланировали какую-то работу повторить фиксированное количество раз:

1
2
3
4
5
6
7
8
9
val trigger = newTrigger().
    startAt(dateOf(9, 0, 0)).
    withSchedule(
        simpleSchedule().
            withRepeatCount(7).
            withIntervalInHours(1).
            WithMisfireHandlingInstructionFireNow()  //or other
    ).
    build()

В этом примере предполагается, что триггер срабатывает 8 раз (первое выполнение + 7 повторений) каждый час, начиная с startAt(dateOf(9, 0, 0)) сегодня ( startAt(dateOf(9, 0, 0)) . Таким образом, последнее выполнение должно произойти в 16:00. Однако Предположим, что по какой-то причине планировщик был не в состоянии выполнять задания в 9 и 10 часов утра, и он обнаружил этот факт в 10:15 утра, то есть произошел сбой 2 разрядов. Как поведет себя планировщик в этой ситуации?

Простой триггер повторяется бесконечно

В этом сценарии триггер повторяется бесконечное количество раз с заданным интервалом:

1
2
3
4
5
6
7
8
9
val trigger = newTrigger().
    startAt(dateOf(9, 0, 0)).
    withSchedule(
        simpleSchedule().
            withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY).
            withIntervalInHours(1).
            WithMisfireHandlingInstructionFireNow()  //or other
    ).
    build()

И снова триггер должен срабатывать каждый час, начиная с 9 часов утра сегодня ( startAt(dateOf(9, 0, 0)) . Однако планировщик не был способен выполнять задания в 9 и 10 часов утра и обнаружил этот факт в 10:15. AM, т. Е. 2 ​​срабатывания сработали. Это более общая ситуация по сравнению с простым триггером, запускаемым фиксированное количество раз.

CRON триггеры

Триггеры CRON являются наиболее популярными среди пользователей Quartz. Однако есть также два других доступных триггера: DailyTimeIntervalTrigger (например, срабатывание каждые 25 минут ) и CalendarIntervalTrigger (например, срабатывание каждые 5 месяцев ). Они поддерживают политики запуска, невозможные как в CRON, так и в простых триггерах. Однако они понимают те же инструкции по обработке пропуска зажигания, что и триггер CRON.

1
2
3
4
5
6
val trigger = newTrigger().
 withSchedule(
  cronSchedule("0 0 9-17 ? * MON-FRI").
   withMisfireHandlingInstructionFireAndProceed()  //or other
 ).
 build()

В этом примере триггер должен срабатывать каждый час с 9:00 до 17:00 с понедельника по пятницу. Но еще раз первые два вызова были пропущены (поэтому триггер сработал), и эта ситуация была обнаружена в 10:15. Обратите внимание, что доступные команды пропуска зажигания отличаются от простых триггеров:


QTZ-283 Примечание : QTZ-283: MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY не работает с JDBCJobStore — очевидно, существует ошибка при использовании JDBCJobStore , следите за этой проблемой.

Как видите, различные триггеры ведут себя по-разному в зависимости от фактической настройки. Более того, несмотря на то, что предусмотрена так называемая « умная политика» , зачастую решение основывается на требованиях бизнеса. По сути, есть три основные стратегии: игнорировать , запустить немедленно и продолжить, сбросить и ждать следующего . Все они имеют разные варианты использования:

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

Используйте политики * сейчас, когда есть задания, выполняемые периодически, а в случае пропуска зажигания они должны запускаться как можно скорее, но только один раз. Подумайте о работе, которая очищает каталог /tmp каждую минуту. Если планировщик был занят в течение 20 минут и, наконец, может выполнить это задание, вы не хотите запускать его 20 раз! Одного достаточно, но убедитесь, что он работает так быстро, как может. Затем вернитесь к обычным минутным интервалам.

Наконец, следующие * правила хороши, если вы хотите, чтобы ваша работа выполнялась в определенные моменты времени. Например, вам нужно получать цены на акции каждый квартал. Они быстро меняются, поэтому, если ваша работа не сработала, а уже прошло 20 минут после часа, не беспокойтесь. Вы пропустили правильное время на 5 минут, и теперь вам все равно. Лучше иметь пробел, чем неточную стоимость. В этом случае Quartz пропустит все пропущенные исполнения и просто дождется следующего.

Справка: инструкции по пропуску кварцевого планировщика, объясненные нашим партнером по JCG Томашем Нуркевичем в блоге Java и соседей .