Введение
Table of Contents
Агенты - технология, позволяющая запускать произвольные PHP функции (агенты) с заданной периодичностью. Технически агент - это запись в специальной таблице состоящая из:
- выполняемого кода,
- даты выполнения,
- периода выполнения,
- каким способом назначать время следующего запуска агента (см. ‘Периодические и непериодические’).
Агенты и почтовые события работают на основе механизма “Фоновых заданий” и выполняются после выполнения страницы.
До версии 20.5.0 агенты выполнялись в самом начале загрузки каждой страницы (непосредственно перед открытием сессии и событием OnPageStart) система автоматически проверяет, есть ли агент, который нуждается в запуске, и исполняет его в случае необходимости.
И если “агенты” это технология, то что же физически из себя представляет агент?
Это php-код, занимающий в ячейки таблицы не более 64Кбайт текста, который будет выполнен через eval
-функцию языка один или некоторое количество раз.
При выпонении агент обязан вернуть строковый результат: либо пустая строка (не продолжать выполнение), либо название следующего агента.
И хотя технически в код агента можно записать циклы, запросы и т.д. не рекомендуется создавать агенты который состоят из чего-то большего чем вызов функции или статического метода класса.
Периодические и непериодические виды агентов
Условно все агенты можно разделить на две большие группы по характеру выполнения: “периодические” (“точно в указанное время”) и “непериодические” (“через заданный интервал”), хотя правильнее было бы сказать: “неповторяющиеся” и “повторяющиеся”. Исторически сложившиеся названия решили не менять.
Периодические агенты
Агенты выполняющиеся “точно в указанное время”, т.е. их интервал вычисляется по формуле:
Время назначенного следующего выполнения = Старое назначенное время агента + интервал
Например, раз в день запускается агент очистки старых почтовых событий или агент с ежедневным отчетом о посещаемости сайта.
Важное примечание: агенты выполняется строгое количество раз. Что это значит? Если агенты работают на хитах и с момента последнего хита было пропущено Н выполнений, то после подачи хитов на сайт, агент будет выполнен Н раз
Важное примечание: На проектах с низкой посещаемостью возможна ситуация, когда большое количество агентов может тормозить работу сайта. Монитор производительности показывает низкую (в районе 1) оценку. Статистика выполнения страниц показывает время генерации от 1 секунды и больше, а некоторые страницы вообще отказываются открываться. При таких симптомах посмотрите внимательнее статистику выполнения страницы. Если обнаружится что 90% времени генерации страницы занимает пролог сайта, то вероятно проблема в агентах. Достаточно 5-и агентов, у которых выставлено свойство Периодический. При выставлении этого свойства система при пропуске выполнения агента в следующие разы пытается компенсировать пропуски, что приводит к перегрузке сервера. Решение: Выключить периодичность агентов.
Непериодические агенты
Агенты выполняющиеся “через заданный интервал”, т.е. их интервал вычисляется по формуле:
Время назначенного следующего выполнения = Время завершения последнего запуска + интервал
Важное примечание: в отличие от периодического вида агентов в подобной ситуации агент поведет себя подругому - будет вызван только один раз.
Абсолютное большинство агентов непериодические.
Механизмы запуска агентов
В Битрикс24 существует несколько механизмов запуска агентов:
- Выполнение всех агентов на хитах
- Выполнение всех агентов на cron
- Комбинированный способ: непериодические агенты на
cron
, периодические на хитах (по-умолчанию для Bitrix Env)
Почему существует несколько механизмов запуска? Битрикс24 разработан на Bitrix Framework, который имеет богатую историю и печальную славу “legacy”-кода. Механизм работы агентов на хитах был использован в тот момент, когда настройка cron была прирогативой разработчиков, а продукт позиционировался как максимально удобный для пользователя. Т.е. он должен был работать одинаково везде - и на хостинг-провайдерах и на VPS. Изначально такой независимой технологией и были - агенты, выполняемые на хитах.
Почему нельзя ультимативно перевести все на cron? Не смотря на наличие технической возможности выполнять все на cron, некоторые задачи могут требовать более частого выполнения, в то время как минимальный интервал крона - 1 минута. К тому же не стоит забывать, что все так же есть установки на разных платформах, где cron может быть закрыт для установки.
Подробнее о механизме запусков можно узнать в документации (см. ссылку в подвале “Запуск агентов”).
Применение
Использовать агенты удобно для выполнения любых операций связанных с повторением или фоновыми (не зависящими от пользователей) действиями. В Битриксе агенты имеют самое разнообразное применение:
-
CUser::CleanUpAgent();
- агент удаляющий пользователей с неподтвержденной регистрацией если прошло больше Н дней после регистрации из главного модуля -
\Bitrix\Tasks\Internals\Counter\Agent::expiredSoon(6);
- агент уведомляющий пользователя о скором окончании задач для пользователя ID:6 из модуля задач -
CTicket::AutoClose();
- агент автоматический закрывающий задачи из модуля техподдержка -
CCalendar::ReminderAgent(16, 1, 'https://some-portal.tech/company/personal/user/1/calendar/?EVENT_ID=16', 'user', 1, 0);
- агент отправляющий нотификацию пользователю ID:1 по событию ID:16 (EVENT_ID
)
Как видно - агенты достаточно распространенная и широкоприменя в продукте технология, однако как и любая другая технология она имеет свои ограничения.
Ограничения
Разработчик должен учитывать, что код, который будет завернут в агент имеет достаточно много ограничений:
- Объекта пользователя (
$USER
) в агентах нет. Поэтому любой код, который запрашивает должен проверяться на наличие опций отключающих проверку прав. - Отсутствует константа
SITE_ID
. Агент может выполнять не только на страницах конкретного сайта, но и на хитах в административной панели и даже в cli-режиме (например на cron). - На многоязычных сайтах нельзя заранее узнать какой будет язык.
- Агенты выполняются в однопоточном режиме с блокировкой на MySQL на 10 минут. Новый вызов агента возможен только после того, как отработает предыдущий вызов. Блокировка БД может потеряться если закроется соединение с ней. 10 минут ожидания это - дополнительная защита от повторного запуска, агентов которые не корректно отработали.
- В случае выполнения агентов на хитах временная точность запуска агентов напрямую зависит от равномерности и плотности посещаемости сайта. Реальное время запуска агента обычно чуть-чуть позже, чем время, на которое назначен агент (при равномерной посещаемости). Момент запуска - это когда кто-то зашел на страницу сайта. Если вам необходимо организовать запуск каких-либо PHP функций в абсолютно точно заданное время, то необходимо воспользоваться стандартной утилитой cron, предоставляемой большинством хостингов.
- Кроме этого, не рекомендуется вешать на агенты ресурсоёмкие операции, для них существует фоновый запуск по cron’у.
Важно: В агентах нельзя проводить авторизацию методом
Authorize
, поскольку она может отправить cookie файлы авторизации случайному посетителю.
Примечание: даже наличие глобальной переменной
$USER
не является гарантией того, что в этой переменной находится объект классаCUser
Выбор между агентами и cron
У опытных разработчиков обычно не возникает сомнений в выборе способа реализации той или иной бизнес-функции, однако есть общие рекомендации по выбору:
- Ресурсоемкие операции (выполняющиеся 5+ секунд или требующие значительного объема оперативной памяти) следует выполнять отдельными
cron
-скриптами. - Если это будет использоваться в тиражируемом модуле, то выбор стоит отдать в пользу агентов.
- Во всех остальных случаях предпочтение стоит отдать агентам.
Да, последний пункт является “вкусовщиной” и нет единого мнения как стоит реализовывать тот или иной код. Я предпочитаю использовать cron-скрипты, вместо агентов и использую последних только в тиражируемых модулях или когда это выгоднее с точки зрения времени на написание.
API
Поскольку агенты это системный механизм у него присутствует 2 набора публичного api: верхнеуровневый и низкоуровневый, однако не смотря на наличие обоих API в доступности строго не рекомендуется использовать низкий уровень.
Создание и редактирование агента
В создании агента участвует 2 статических метода: высокоуровневый CAgent::AddAgent
и низкоуровневый CAgent::Add
.
Сигнатура высокоуровневного метода:
CAgent::AddAgent(
$name, // PHP function name
$module = "", // module
$period = "N", // check for agent execution count in period of time
$interval = 86400, // time interval between execution
$datecheck = "", // first check for execution time
$active = "Y", // is the agent active or not
$next_exec = "", // first execution time
$sort = 100, // order
$user_id = false, // user
$existError = true // return error, if agent already exist
);
Сигнатура низкоуровневого метода:
CAgent::Add($arFields);
Примечательным является тот факт, что метод AddAgent
по факту выполняет обертку над методом Add
, проверяя дубли по названию агента и пользователю, т.е. выполнение 2 и более раза метода \CAgent::AddAgent
не создаст несколько агентов, а повторное выполнение вернет false
, вместо идентификатора добавленного агента.
Предположим у нас есть собственный модуль “Технической поддержки” vendor.support
, с агентом который закрывает все тикеты, на которые нет ответа более недели.
Мы разработали идемпотентный код \Vendor\Support\Ticket::AutoClose();
и из консоли php разработчика он прекрасно работает. Теперь мы хотим создать агент который будет работать 24 часа для автозакрытия таких тикетов.
\CAgent::AddAgent(
"\Vendor\Support\Ticket::AutoClose();", // имя функции
"vendor.support", // идентификатор модуля
"N", // агент не критичен к кол-ву запусков
86400, // интервал запуска - 1 сутки
"", // дата первой проверки - текущее
"Y", // агент активен
"", // дата первого запуска - текущее
30 // сортировка (по-умолчанию 100)
);
Важно отметить, что имя функции подается с точкой с запятой на конце. Это должен быть валидный php-код, который исполняется через
eval
Что касается обновления агентов, здесь представлен только низкоуровневый метод CAgent::Update($agentId, $arFields)
, который аналогичен другим update
методам старого ядра.
Удаление агентов
Удаление агентов представлено более широким ассортиментом методов: CAgent::RemoveAgent()
, CAgent::Delete()
и CAgent::RemoveModuleAgents()
.
В отличии от CAgent::AddAgent
метод CAgent::RemoveAgent()
не вызывает внутри себя метод CAgent::Delete
, а является более широким по возможностям методом.
CAgent::RemoveAgent
- удаление одного конкретного агента по имени, с возможностью дополнительного ограничения по модулю и пользователю.
Сигнатура метода:
\CAgent::RemoveAgent(
$name,
$module = "",
$user_id = false
);
\CAgent::Delete($id)
- удаление агента по его идентификатору в таблице b_agent
\CAgent::RemoveModuleAgents($module)
- удаление всех агентов, привязанных к модулю $module
.
Получение агентов
Методы для получения списка агентов традиционно для старого ядра осуществляются двумя статическими методами: \CAgent::GetById
и \CAgent::GetList
.
Поскольку метод \CAgent::GetById($ID)
так же традиционно является прослойкой в \CAgent::GetList
с фиксированным фильтром, то рассматривать его нет никакого смысла.
Что же касается метода \CAgent::GetList
, то он несколько отличается от техже методов элемента инфоблоков.
Сигнатура метода:
\CAgent::GetList($arOrder = Array("ID" => "DESC"), $arFilter = array());
Он имеет следующие особенности:
- На выход принимаются только 2 параметра: Порядок (Сортировка,
$arOrder
) и Фильтр ($arFilter
). Указать постраничную навигацию, группировку или интересующие поля вы не сможете. - В фильтре не работает сложная логика. Допустимые параметры фильтрации:
Код | Пояснение |
---|---|
ID | Строгое сравнение по идентификатору |
ACTIVE | Если значнение Y или N то строгое сравнение. Любое другое значение - игнорирование параметра фильтрации |
IS_PERIOD | Аналогично ACTIVE параметру |
NAME | LIKE-сравнение со значением |
=NAME | Строгое сравнение по названию |
MODULE_ID | Строгое сравнение по модулю |
USER_ID | Если больше 0 - поиск по пользователю, если любое другое значение - поиск агента с NULL-указанным пользователем |
LAST_EXEC | Дата (без времени) последнего выполнения агента |
NEXT_EXEC | Дата (без времени) следующего выполнения агента |
Несмотря на схождесть LAST_EXEC
и NEXT_EXEC
, в многосайтовых и кластерных конфигурациях есть некоторое различие в парсинге аргументов.
LAST_EXEC
- принимает дату в формате языка (CLang::GetDateFormat()
), в то время как NEXT_EXEC
будет ожидать дату в формате определенной в константе FORMAT_DATETIME
.
В обычном случае это один и тот же формат даты, но в сложных конфигурациях это может сыграть злую шутку.
Важное примечание: если в фильтре какому-нибудь ключу будет соответствовать пустое строковое значение, то такой ключ фильтра будет проигнорирован.
Точно так же, как если в ключ USER_ID
на данный момент не обрабатывается.
Отладка агентов
Иногда возникает необходимость отладить какие-то сложные механизмы или алгоритмы и для этого существует debug-функция агентов.
Шаг 1. Определяем в проекте произвольную функцию со следующей сигнатурой:
function agentDebug($arAgent, $strEvent, $strEvalResult = null, $error = ""): void
Пояснение к аргументам:
-
$arAgent
- данные агента из таблицыb_agent
-
$strEvent
- код события, поясняющего момент вызова (start
- инициализация,finish
- успешное завершение,not_callable
- неопределенное поведение) -
$strEvalResult
- результат выполнения агента -
$error
-Throwable
-наследник.
Шаг 2.
Определяем константу BX_AGENTS_LOG_FUNCTION
с названием нашей лог-функции:
define('BX_AGENTS_LOG_FUNCTION', 'agentDebug');
Шаг 3.
Ждем пока исполнятся агенты и любуемся результатом.
Важное примечание: в последних релизах (21 и 22) механизм отладки агентов - сломан. Если агент во время выполнения бросит исключение, то события с кодами
finish
иnot_callable
не будут вызваны! Вместо этого нужно смотреть Exception-лог из главного модуля.
Пример функции-логирования для размещения в legacy.php
(см. структуру проекта)
function agentDebug($arAgent, $strEvent, $strEvalResult = null, $error = "")
{
\Bitrix\Main\Diag\Debug::dumpToFile(
[
'$arAgent' => $arAgent,
'$strEvent' => $strEvent,
'$strEvalResult' => $strEvalResult,
'$error' => $error,
],
"data",
str_replace($_SERVER['DOCUMENT_ROOT'], "", __DIR__."/log.log")
);
}
define('BX_AGENTS_LOG_FUNCTION', 'agentDebug');
Дополнительная информация
Агенты в курсе Bitrix Framework Агенты в Пользовательской документации Запуск агентов