Планировщик заданий Quartz

Сложный

Рекомендации по настройке планировщика заданий 

Все политики Quartz по обработке не отработавших вовремя заданий можно разделить условно на три группы: Ignore misfire policy (Игнорировать), Run immediately and continue (Выполнить немедленно и продолжать) и Discard and wait for next (Отменить и ожидать следующего задания). Далее приведены рекомендации, в каких случаях использовать ту или иную политику.

Ignore misfire policy 

Эта политика представлена одноименной константой MisfireInstruction.IgnoreMisfirePolicy = -1. Ее рекомендуется применять в том случае, когда необходимо гарантировать, что все запуски триггера будут обязательно выполнены, даже если имеется несколько просроченных запусков триггера. Например, есть задание А с периодичностью в 2 минуты. Из-за нехватки рабочих потоков Quartz или выключения планировщика время следующего старта задания A (NEXT_FIRE_TIME) отстает на 10 минут от текущего времени. Если необходимо, чтобы все 5 просроченных запусков задания обязательно выполнились, то следует использовать политику IgnoreMisfirePolicy.

Эту политику рекомендуется применять для триггеров, которые при каждом запуске используют некоторые уникальные данные, и важно, чтобы все запуски триггера были рано или поздно выполнены. Например, есть задание B, которое выполняется с интервалом в 1 час и генерирует отчет за интервал времени от PREV_FIRE_TIME до PREV_FIRE_TIME + 1 час. При этом планировщик был выключен на 8 часов. В таком случае необходимо, чтобы после включения планировщика все 8 просроченных запусков задания B были в конечном итоге запущены, и таким образом сгенерированы отчеты за каждый из часов простоя.

Следует также отметить, что применение этой политики к триггерам, которые не привязаны к каким-либо уникальным данным, может привести к ненужному загромождению очереди планировщика и ухудшению общей производительности приложения. Например, для каждого из пользователей Сreatio настроена синхронизация почты Exchange с интервалом в 1 минуту. При этом 1.5 часа выполнялось обновление. После обновления Quartz, прежде чем приступить к заданиям, запланированным на текущее время, будет выполнять для каждого из пользователей синхронизацию почты 90 раз. Хотя достаточно выполнить просроченную синхронизацию почты однократно, а затем вернуться к выполнению задания согласно расписанию.

Run immediately and continue 

К этой группе политик относятся:

  • SimpleTrigger.FireNow;
  • SimpleTrigger.RescheduleNowWithExistingRepeatCount;
  • SimpleTrigger.RescheduleNowWithRemainingRepeatCount;
  • CronTrigger.FireOnceNow;
  • CalendarIntervalTrigger.FireOnceNow.

Эти политики следует применять, если необходимо просроченное задание выполнить как можно скорее, но один раз, а далее выполнять задание согласно расписанию. В такую категорию входят, например, задания по синхронизации почты (такие, как <user>@<server>_LoadExchangeEmailsProcess_<userId>, SyncImap_<user>@<server>_<userId>), а также задачи RemindingCountersJob, SyncWithLDAPProcess.

Например, для каждого из пользователей настроена синхронизация почты с интервалом 5 минут, запускаемая триггерами <user>@<server>_LoadExchengeEmailProcess_<userId>Trigger. При этом сайт обновлялся с 1:30 до 2:43. Когда сайт был запущен (в 2:43), время следующего запуска для триггеров <user>@<server>_LoadExchengeEmailsProcess_<userId>Trigger будет обновлено на текущее (т. е., на 2:43). Соответственно, просроченные задания будут запущены один раз в 2:43 и далее будут выполняться согласно расписанию (т. е., в 2:48, 2:53, 2:58 и т. д.).

Discard and wait for next 

К этой группе политик относятся:

  • SimpleTrigger.RescheduleNextWithRemainingCount;
  • SimpleTrigger.RescheduleNextWithExistingCount;
  • CronTrigger.DoNothing;
  • CalendarIntervalTrigger.DoNothing.

Эти политики следует применять для заданий, которые необходимо выполнять строго в определенное время. Например, есть задание по сбору статистики, запускаемое каждый день в 3 часа ночи, когда нет активных пользователей на сайте (используется CronTrigger). Это задание достаточно ресурсоемкое и длительное, и в рабочее время его запускать нельзя, потому что это может замедлить работу пользователей. В таком случае нужно применить политику CronTrigger.DoNothing. В итоге, если по какой-то причине задание не было выполнено вовремя, следующий запуск будет запланирован на 3 часа ночи следующих суток.

Конфигурирование Quartz 

thread count 

Если планировщик не успевает своевременно обрабатывать задания или некоторые задания не выполнились ни разу, то, возможно, поможет увеличение числа потоков Quartz. Для этого необходимо установить необходимое количество потоков в файле Web.config загрузчика приложения.

Установка необходимого количества потоков в файле Web.config загрузчика приложения
<add key="quartz.threadPool.threadCount" value="5" />

Важно. Файл Web.config загрузчика приложения расположен в корневом каталоге установленного приложения Creatio.

misfireThreshold 

Если увеличение числа потоков Quartz нежелательно (например, из-за ограниченных ресурсов), то оптимизировать выполнение заданий может изменение настройки misfireThreshold в файле Web.config загрузчика приложения.

Например, есть приложение с числом заданий, намного большим, чем число потоков. При этом большинство заданий выполняются с достаточно малым интервалом (1 минута). Значение настройки misfireThreshold равно одной минуте, число потоков равно 3.

Изменение настройки misfireThreshold в файле Web.config загрузчика приложения
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.threadPool.threadCount" value="3" />

Также для большинства заданий используются политики из группы Run immediately and continue. Это означает следующее. Quartz для большинства заданий, у которых MISFIRE_INSTR не равно -1 и NEXT_FIRE_TIME меньше текущего времени на 1 минуту (60000 мс), будет регулярно устанавливать время следующего запуска на текущее время. Это означает, что будет теряться изначальный порядок запланированных заданий, потому что всем заданиям будет установлено время запуска на текущее время. Как следствие, увеличивается вероятность того, что Quartz чаще будет брать в работу одни и те же задания, при этом игнорируя другие.

На рисунке отображена очередь планировщика после 15 минут работы. Задания, у которых значение PREV_FIRE_TIME равно NULL, ни разу не выполнялись. Очевидно, что таких заданий достаточно много.

Затем следует увеличить значение misfireThreshold до 10 минут (очистив при этом PREV_FIRE_TIME в QRTZ_TRIGGERS).

queue_01.png
Увеличение значения misfireThreshold
<add key="quartz.jobStore.misfireThreshold" value="600000" />

После 15 минут работы планировщика очередь будет выглядеть следующим образом:

queue_02.png

Очевидно, что ни разу не запущенных заданий стало намного меньше.

Увеличение misfireThreshold приводит к тому, что планировщик более равномерно выполняет задания. Другими словами, выполняются практически все задания из очереди. Очевидно, что из-за нехватки потоков планировщик не успевает каждое из заданий выполнять через минуту. Это видно по колонке [Last repeat interval], значение в которой равно NEXT_FIRE_TIME - PREV_FIRE_TIME, мин. Однако, при этом планировщик выполняет каждое из заданий.

batchTriggerAcquisitionMaxCount 

Увеличение batchTriggerAcquisitionMaxCount может оптимизировать работу планировщика, если не используется кластеризованная конфигурация Quartz (используется один узел планировщика).

Политики Quartz для обработки не отработавших вовремя заданий 

В Quartz существуют как политики, общие для всех типов триггеров, так и политики, специфичные для конкретного типа триггера. В таблице перечислены все политики, используемые для триггеров SimpleTrigger, CronTrigger и CalendarIntervalTrigger.

Политики триггеров
Политика Quartz Значение MISFIRE_INSTR Значение Terrasoft.Core.Scheduler.AppSchedulerMisfireInstruction Тип триггера
IgnoreMisfirePolicy -1 IgnoreMisfirePolicy для всех типов

Описание поведения IgnoreMisfirePolicy

Триггеры с IgnoreMisfirePolicy будут обязательно выполнены. Соответственно, для таких триггеров Quartz не будет обновлять время следующего запуска (NEXT_FIRE_TIME).

Все просроченные задания Quartz попытается выполнить как можно скорее, после чего вернется к изначальному расписанию триггера. Например, запланировано задание с триггером SimpleTrigger на 10 повторений. Исходные условия:

  • START_TIME = 9:00;
  • REPEAT_COUNT = 9 (первое выполнение + 9 повторений);
  • REPEAT_INTERVAL = 0:15.

Если планировщик был выключен с 8:50 до 9:20, то при включении Quartz попытается как можно скорее выполнить 2 просроченных задания (в 9:00 и 9:15). Далее он будет выполнять 8 оставшихся заданий по расписанию — в 9:30; 9:45 и т.д.

SmartPolicy 0 SmartPolicy для всех типов

Описание поведения SmartPolicy

Используется Quartz по умолчанию для всех типов триггеров. В зависимости от типа и конфигурации триггера Quartz выберет соответствующую политику. В псевдокоде ниже приведен алгоритм выбора для версии Quartz 2.3.2.

if (TRIGGER_TYPE == 'SIMPLE') // Триггер Simple.
    if (REPEAT_COUNT == 0) // Без повторов.
        MISFIRE_INSTR = 1 // SimpleTrigger.FireNow
    else if (REPEAT_COUNT == -1) // Повторять бесконечно.
        MISFIRE_INSTR = 4 // SimpleTrigger.RescheduleNextWithRemainingCount
    else // Указано количество повторов.
        MISFIRE_INSTR = 2 // SimpleTrigger.RescheduleNowWithExistingRepeatCount
else if (TRIGGER_TYPE == 'CAL_INT') // Триггер СalendarInterval.
    MISFIRE_INSTR = 1 // CalendarIntervalTrigger.FireOnceNow
else if (TRIGGER_TYPE == 'CRON') // Триггер Cron.
    MISFIRE_INSTR = 1 // CronTrigger.FireOnceNow
			
SimpleTrigger.FireNow 1 FireNow SimpleTrigger

Описание поведения SimpleTrigger.FireNow

Применяется для триггеров SimpleTrigger у которых значение REPEAT_COUNT равно 0 (триггеры, рассчитанные на 1 запуск). Если значение REPEAT_COUNT не равно 0, то будет применена политика SimpleTrigger.RescheduleNowWithRemainingRepeatCount.

Например, запланировано задание с триггером SimpleTrigger. Исходные условия:

  • START_TIME = 9:00;
  • REPEAT_COUNT = 0.

Если планировщик был выключен с 8:50 до 9:20, то при включении Quartz попытается как можно скорее выполнить задание. В результате, в 9:20 или чуть позже задание будет выполнено.

SimpleTrigger.RescheduleNowWithExistingRepeatCount 2 RescheduleNowWithExistingRepeatCount SimpleTrigger

Описание поведения SimpleTrigger.RescheduleNowWithExistingRepeatCount

Планировщик пытается как можно скорее выполнить первое просроченное задание. Все остальные запуски триггера будут выполнены с интервалом REPEAT_INTERVAL.

Например, запланировано задание с триггером SimpleTrigger на 10 повторений. Исходные условия:

  • START_TIME = 9:00;
  • REPEAT_COUNT = 9;
  • REPEAT_INTERVAL = 0:15.

Если планировщик был выключен с 8:50 до 9:20, то при включении Quartz выполнит первое просроченное задание, назначенное на 9:00, (из заданий в 9:00 и 9:15) в 9:20. Оставшиеся 9 запусков выполнит в 9:35, 9:50 и т.д. .

На заметку. Для методов AppScheduler.ScheduleMinutelyJob поведение RescheduleNowWithExistingRepeatCount идентично RescheduleNowWithRemainingRepeatCount, а поведение RescheduleNextWithRemainingCount идентично RescheduleNextWithExistingCount, поскольку используются триггеры с REPEAT_COUNT = -1.

SimpleTrigger.RescheduleNowWithRemainingRepeatCount 3 RescheduleNowWithRemainingRepeatCount SimpleTrigger

Описание поведения SimpleTrigger.RescheduleNowWithRemainingRepeatCount

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

Например, запланировано задание с триггером SimpleTrigger на 10 повторений. Исходные условия:

  • START_TIME = 9:00;
  • REPEAT_COUNT = 9;
  • REPEAT_INTERVAL = 0:15.

Если планировщик был выключен с 8:50 до 9:20, то при включении Quartz выполнит первое просроченное задание (из заданий в 9:00 и 9:15) в 9:20. Второе просроченное задание будет проигнорировано, и оставшиеся 8 запусков будут выполнены в 9:35, 9:50 и т.д.

SimpleTrigger.RescheduleNextWithRemainingCount 4 RescheduleNextWithRemainingCount SimpleTrigger

Описание поведения SimpleTrigger.RescheduleNextWithRemainingCount

Планировщик игнорирует просроченные задания и ждет следующего планового запуска задания. При наступлении времени следующего запуска будут выполнены оставшиеся не просроченные задания с интервалом REPEAT_INTERVAL.

Например, запланировано задание с триггером SimpleTrigger на 10 повторений. Исходные условия:

  • START_TIME = 9:00;
  • REPEAT_COUNT = 9;
  • REPEAT_INTERVAL = 0:15.

Если планировщик был выключен с 8:50 до 9:20, то при включении Quartz выполнит оставшиеся 8 непросроченных заданий в 9:30; 9:45 и т.д.

SimpleTrigger.RescheduleNextWithExistingCount 5 RescheduleNextWithExistingCount SimpleTrigger

Описание поведения SimpleTrigger.RescheduleNextWithExistingCount

Планировщик будет ждать следующего времени запуска и выполнит все оставшиеся задания с интервалом REPEAT_INTERVAL.

Например, запланировано задание с триггером SimpleTrigger на 10 повторений. Исходные условия:

  • START_TIME = 9:00;
  • REPEAT_COUNT = 9;
  • REPEAT_INTERVAL = 0:15.

Если планировщик был выключен с 8:50 до 9:20, то при включении Quartz выполнит все 10 заданий в 9:30; 9:45 и т.д.

CronTrigger.FireOnceNow 1 - CronTrigger

Описание поведения CronTrigger.FireOnceNow

Планировщик пытается как можно скорее выполнить первое просроченное задание. Остальные просроченные задания игнорируются. Оставшиеся непросроченные задания планировщик выполняет согласно с расписанием.

Например, запланировано задание с триггером CronTrigger: CRON_EXPRESSION = '0 0 9-17 ? * MON-FRI' (с понедельника по пятницу с 9:00 до 17:00). Если планировщик был выключен с 8:50 до 10:20, то при включении в 10:20 Quartz выполнит первое просроченное задание из двух (в 9:00 и 10:00). Далее задания будут выполняться в 11:00, 12:00 и т.д.

CronTrigger.DoNothing 2 - CronTrigger

Описание поведения CronTrigger.DoNothing

Планировщик игнорирует все просроченные задания. Оставшиеся непросроченные задания выполняются согласно с расписанием.

Например, запланировано задание с триггером CronTrigger: CRON_EXPRESSION = '0 0 9-17 ? * MON-FRI' (с понедельника по пятницу с 9:00 до 17:00). Если планировщик был выключен с 8:50 до 10:20, то при включении Quartz начнет выполнять задания с 11:00 (в 11:00, 12:00 и т.д).

CalendarIntervalTrigger.FireOnceNow 1 - CalendarIntervalTrigger

Описание поведения CalendarIntervalTrigger.FireOnceNow

Поведение аналогично CronTrigger.FireOnceNow.

CalendarIntervalTrigger.DoNothing 2 - CalendarIntervalTrigger

Описание поведения CalendarIntervalTrigger.DoNothing

Поведение аналогично CronTrigger.DoNothing.

Особенности работы с планировщиком при использовании горизонтального масштабирования описаны в статье Настроить горизонтальное масштабирование.