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 в интернете, сети или на компьютере.
На графике выше показано, что 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 принимает следующие параметры:
- Ключ идентификации задачи:
key. - Настройка времени расписания для выполнения.
- Адрес сервиса, который должен выполняться, то есть
URL. - Дополнительные параметры, передаваемые при вызове сервиса.
_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в примере выше.