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

Cron Jobs - Запланированные сервисы

Перед началом​

Прежде чем продолжить, убедитесь, что у вас есть базовые знания JavaScript и основы Netuno, пройдя пошаговое руководство, доступное в Академии.

Введение​

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

В Linux/UNIX/Mac существует служба Cron с известным файлом конфигурации crontab, а в Windows есть Task Manager (Диспетчер задач).

А в Netuno Cron Jobs уже встроен, что помогает избежать внешней настройки.

Такие периодические операции известны как Cron Jobs, или по-русски — запланированные сервисы.

Каждое приложение Netuno может иметь множество Cron Jobs, и Cron Jobs Netuno могут выполнять как внутренние сервисы и URL, так и внешние URL.

Для получения дополнительной информации см. документацию ресурса cron.

Как это работает​

Каждое приложение имеет настройку собственных запланированных сервисов (cron jobs) с соответствующей конфигурацией выполнения.

Для простоты каждый запланированный сервис — это отдельный job, имеющий собственную конфигурацию выполнения: он может выполняться каждую минуту, каждые 5 минут, каждый час, в определённый день и час недели и т. д. ... также у него есть URL, который будет вызван в момент его выполнения.

Netuno использует библиотеку Quartz Scheduler для управления выполнением запланированных сервисов. Настройка правил временного планирования соответствует этой библиотеке.

Таким образом, выполнение каждой задачи проходит через вызов URL (веб-адреса, ссылки), который может быть как внешним, так и внутренним для самого приложения. Если это внешняя ссылка, она может принадлежать другому приложению или любому внешнему URL в интернете, сети или на компьютере.

Схема Cron

На графике выше показано, что Netuno содержит 2 приложения, в каждом из которых настроено 2 задачи (запланированных сервиса), выполняемые в разное время согласно некоторой конфигурации.

Поведение выполнения будет следующим:

  • Приложение 1 запускает 2 задачи:
    • Задача 1: выполняет URL внутреннего сервиса самого Приложения 1.
    • Задача 2: выполняет внешний публичный URL.
  • Приложение 2 запускает 2 задачи:
    • Задача 3: выполняет URL внутреннего сервиса самого Приложения 2.
    • Задача 4: выполняет URL сервиса Приложения 1.

Приложения должны размещать сервисы, выполняемые Cron Jobs, в папке:

  • 📂 server/services/jobs

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

Настройка​

Настройка запланированных сервисов выполняется в файле конфигурации приложения в соответствии с окружением.

Настройка никогда не должна начинаться с *, это вызовет выполнение каждую секунду, что создаст проблемы с производительностью по принципу снежного кома, если обработка в какой-то момент займёт больше 1 секунды.

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

  • 📂 config/_development.json

Проверьте, нет ли уже настроек для Cron; если их нет, добавьте в конец файла конфигурации (в формате JSON) параметры настройки:

{
...
"cron": {
"secret": "m3u-S3Gred0",
"jobs": [
{
"name": "moy-job",
"config": "0 30 12 * * ?",
"url": "http://moya-app.local.netu.no:9000/services/jobs/moy-job"
},
{
"name": "moy-job",
"config": "0 0/2 * * * ?",
"url": "http://moya-app.local.netu.no:9000/services/jobs/moy-drugoy-job",
"params": {
"secret": "m3u-Ou7ro-S3Gred0",
"тип": "клиент"
}
}
]
},
...
}

В этой конфигурации первая задача будет выполняться каждый день в 12:30.

А вторая задача будет выполняться каждые 2 минуты.

Jobs​

Список запланированных сервисов в приложении; поскольку это массив, можно настроить несколько сервисов.

Secret​

Это параметр ключа безопасности, который защищает сервис: он выполняется только при получении этого параметра с точно совпадающим значением.

Это потому, что jobs — публичные сервисы, и чтобы предотвратить их внешнее выполнение, добавлена эта защита.

Name​

Имя, идентифицирующее сервис внутри системы; может использоваться для управляющих операций, например остановки и возобновления.

Config​

Настройка выражения временного планирования сервиса, то есть правила его расписания.

Настройка никогда не должна начинаться с *, это вызовет выполнение каждую секунду, что создаст проблемы с производительностью по принципу снежного кома, если обработка в какой-то момент займёт больше 1 секунды.

Ниже приведены поддерживаемые выражения.

URL​

Адрес, который будет выполняться периодически.

Params​

Это параметры, передаваемые URL при его выполнении; URL выполняется методом HTTP POST, поэтому можно добавлять дополнительные параметры, например тип в примере выше, или даже указать другой секретный ключ.

Поддерживаемые форматы выражений Cron​

ИМЯ ПОЛЯОБЯЗАТЕЛЬНОДОПУСТИМЫЕ ЗНАЧЕНИЯРАЗРЕШЁННЫЕ СПЕЦИАЛЬНЫЕ СИМВОЛЫ
СекундыДА0-59, - * /
МинутыДА0-59, - * /
ЧасыДА0-23, - * /
День месяцаДА1-31, - * ? / L W
МесяцДА1-12 или JAN-DEC, - * /
День неделиДА1-7 или SUN-SAT, - * ? / L #
ГодНЕТпусто, 1970-2099, - * /
Примеры выражений Cron​
ВЫРАЖЕНИЕЗНАЧЕНИЕ
1 * * * * ?Выполняется в секунду номер 1 каждой минуты.
0 0 12 * * ?Запускается в 12:00 каждый день
0 15 10 ? * *Запускается в 10:15 каждый день
0 15 10 * * ?Запускается в 10:15 каждый день
0 15 10 * * ? *Запускается в 10:15 каждый день
0 15 10 * * ? 2005Запускается в 10:15 каждый день в течение 2005 года
0 * 14 * * ?Запускается каждую минуту с 14:00 до 14:59 каждый день
0 0/5 14 * * ?Запускается каждые 5 минут с 14:00 до 14:55 каждый день
0 0/5 14,18 * * ?Запускается каждые 5 минут с 14:00 до 14:55. Затем запускается каждые 5 минут с 18:00 до 18:55 каждый день
0 0-5 14 * * ?Запускается каждую минуту с 14:00 до 14:05 каждый день
0 10,44 14 ? 3 WEDЗапускается в 14:10 и в 14:44 каждую среду марта.
0 15 10 ? * MON-FRIЗапускается в 10:15 каждый рабочий день недели (с понедельника по пятницу)
0 15 10 15 * ?Запускается в 10:15 15-го числа каждого месяца
0 15 10 L * ?Запускается в 10:15 в последний день каждого месяца
0 15 10 L-2 * ?Запускается в 10:15 в предпоследний день каждого месяца
0 15 10 ? * 6LЗапускается в 10:15 в последнюю пятницу каждого месяца
0 15 10 ? * 6L 2002-2005Запускается в 10:15 каждую последнюю пятницу каждого месяца в течение 2002, 2003, 2004 и 2005 годов
0 15 10 ? * 6#3Запускается в 10:15 в третью пятницу каждого месяца
0 0 12 1/5 * ?Запускается в 12:00 каждые 5 дней каждого месяца, начиная с первого дня каждого месяца.
0 11 11 11 11 ?Запускается каждый год 11 ноября в 11:11.

Настройка никогда не должна начинаться с *, это вызовет выполнение каждую секунду, что создаст проблемы с производительностью по принципу снежного кома, если обработка в какой-то момент займёт больше 1 секунды.

Сервис​

Запланированные сервисы должны быть запрограммированы в папке:

  • 📂 server/services/jobs

Netuno автоматически выполнит эти сервисы и проверит параметр secret на соответствие значению из конфигурации.

secret служит для безопасного выполнения сервиса извне без аутентификации.

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

Чтобы вручную проверить выполнение сервиса, достаточно открыть прямой URL сервиса и передать параметр secret.

Например, через GET:

Вместо ***** в конце URL нужно подставить фактическое настроенное значение secret.

Пример​

Создайте файл по пути:

  • 📂 server/services/jobs/test.js

В этот файл сервиса поместите следующий код:

_log.info("Тестовая задача!")
_out.print("Ок")

В JSON конфигурации вашего приложения настройте параметризацию следующим образом:

{
...
"cron": {
"secret": "m3u-S3Gred0",
"jobs": [
{
"name": "test",
"config": "0/30 * * * * ?",
"url": "/services/jobs/test"
}
]
},
...
}

При запуске сервера Netuno в терминале начнёт появляться сообщение Тестовая задача! каждые 30 секунд.

Если открыть в браузере URL:

Он вернёт только Ок, а в терминале появится сообщение Тестовая задача!.

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

Много задач​

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

Эта настройка выполняется в файле config.js, находящемся в корне Netuno, путём добавления строки из примера:

config.cronThreadCount = 10

По умолчанию эта настройка допускает только 3 потока.

Следует учитывать, что количество потоков нужно увеличивать в соответствии с числом ядер CPU.

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

Поэтому если задачи через некоторое время зависают, вероятно, выполняется слишком много задач параллельно; тогда необходимо увеличить количество потоков.

Программно​

Netuno позволяет настраивать и управлять Cron Jobs программно.

Это означает, что через код можно управлять всем.

Создать новую задачу​

Чтобы создать новую задачу, мы создаём её расписание с помощью _cron.schedule.

_cron.schedule принимает следующие параметры:

  1. Ключ идентификации задачи: key.
  2. Настройка времени расписания для выполнения.
  3. Адрес сервиса, который должен выполняться, то есть URL.
  4. Дополнительные параметры, передаваемые при вызове сервиса.
_cron.schedule(
'moya-zadacha',
'0 0/60 * * * ?',
`/services/jobs/tovar-obnovlenie`,
_val.map()
.set('id', 123)
)

Если нужно автоматически создавать задачи при инициализации приложения, этот автоматизм можно закодировать в файле:

  • server/core/_init.js

Этот файл выполняется каждый раз при инициализации приложения сервером Netuno.

Удалить задачу​

Чтобы удалить определённую задачу, используем _cron.remove(key).

где key — ключ идентификации задачи.

См. пример:

_cron.remove('moya-zadacha')

Список задач​

С помощью _cron.schedules() можно вывести список всех настроенных задач.

Пример вывода списка всех запланированных задач:

const spisok = _cron.schedules()
_out.println('<table>')
_out.println('<tr>')
_out.println(` <td>Ключ</td>`)
_out.println(` <td>URL</td>`)
_out.println(` <td>Параметры</td>`)
_out.println(` <td>Описание</td>`)
_out.println(` <td>Последнее выполнение</td>`)
_out.println(` <td>Следующее выполнение</td>`)
_out.println(` <td>Приоритет</td>`)
_out.println('</tr>')
for (const zadanie of spisok) {
_out.println('<tr>')
_out.println(` <td>${zadanie.getString('key')}</td>`)
_out.println(` <td>${zadanie.getString('url')}</td>`)
_out.println(` <td>${zadanie.getValues('params').toJSON()}</td>`)
_out.println(` <td>${zadanie.get('detail').getDescription()}</td>`)
_out.println(` <td>${zadanie.get('trigger').getPreviousFireTime()}</td>`)
_out.println(` <td>${zadanie.get('trigger').getNextFireTime()}</td>`)
_out.println(` <td>${zadanie.get('trigger').getPriority()}</td>`)
_out.println('</tr>')
}
_out.println('</table>')

Приостановить задачи​

С помощью _cron.pause(key) можно приостановить выполнение определённой задачи.

Пример приостановки всех запланированных задач:

const spisok = _cron.schedules()
_out.println('<table>')
_out.println('<tr>')
_out.println(` <td>Ключ</td>`)
_out.println(` <td>Остановлено</td>`)
_out.println('</tr>')
for (const zadanie of spisok) {
_out.println('<tr>')
_out.println(` <td>${zadanie.getString('key')}</td>`)
_out.println(` <td>${_cron.pause(zadanie.getString('key'))}</td>`)
_out.println('</tr>')
}
_out.println('</table>')

Обратите внимание на использование _cron.pause в примере выше.

Возобновить задачи​

С помощью _cron.resume(key) можно возобновить выполнение определённой задачи.

Пример возобновления всех запланированных задач с отображением времени их выполнения:

const spisok = _cron.schedules()
_out.println('<table>')
_out.println('<tr>')
_out.println(` <td>Ключ</td>`)
_out.println(` <td>Возобновлено</td>`)
_out.println(` <td>Следующее выполнение</td>`)
_out.println('</tr>')
for (const zadanie of spisok) {
_out.println('<tr>')
_out.println(` <td>${zadanie.getString('key')}</td>`)
_out.println(` <td>${_cron.resume(zadanie.getString('key'))}</td>`)
_out.println(` <td>${zadanie.get('trigger').getNextFireTime()}</td>`)
_out.println('</tr>')
}
_out.println('</table>')

Обратите внимание на использование _cron.resume в примере выше.