Агенты изнутри - расписание, хиты, cron, блокировка
Разбираем агенты по слоям: где живёт очередь, как считается время следующего запуска и чем отличается выполнение на хите от выполнения по расписанию сервера. Заодно смотрим, почему однажды агент исчезает из очереди совсем сам.
Механика
Агент - это строка кода с расписанием, а не отдельный процесс. Платформа хранит её вместе с модулем, интервалом и временем следующего запуска в собственной таблице очереди.
Функция агента обязана вернуть платформе строку с командой своего следующего вызова. Возврат пустой строки означает осознанное самоудаление, а забытый возврат приводит к тому же результату молча.
Режимов расписания ровно два, и время следующего запуска они считают по-разному. Интервальный режим отсчитывает паузу от окончания прошлого запуска, а периодический держится жёсткого расписания и догоняет пропущенные слоты.
На хитах очередь агентов разбирает пролог обычной страницы сайта, а не отдельная служба. Значит, точность расписания зависит от посещаемости: ночью на низкопосещаемом сайте очередь просто стоит.
Периодические агенты на редко посещаемом сайте накапливают пропущенные запуски целыми пачками. Дождавшись посетителя, платформа отрабатывает несколько слотов подряд, и этот посетитель ждёт страницу дольше всех остальных.
Агенты платформы однопоточны по своей природе и выполняются строго друг за другом. Долгий агент блокируется от повторного запуска примерно на десять минут, а остальные агенты очереди ждут своей очереди.
Перевод очереди на расписание сервера снимает зависимость от посетителей сайта. Хитовый запуск выключают настройками ядра, а очередь начинает разбирать отдельный скрипт, запускаемый планировщиком.
Внутри агента нет ни пользовательского контекста, ни данных текущего запроса браузера. Ни текущего пользователя, ни адреса страницы там нет, и код, рассчитывающий на них, ведёт себя непредсказуемо.
Ошибка внутри одного агента останавливает разбор всей остальной очереди этого запуска. Следующие агенты в этом запуске не выполняются, и картина выглядит как «перестали работать все агенты сразу».
Фоновая задача - это не агент, а совсем другое средство отложенной работы. Она выполняется один раз сразу после ответа посетителю, тогда как агент возвращается по расписанию.
Шаги
- Прочитать очередь агентов и посмотреть время следующего запуска у каждого из них.
- Определить режим каждого агента: интервал от окончания работы или жёсткое расписание.
- Понять, где выполняется очередь: на хитах посетителей или по расписанию сервера.
- Найти агенты, которые исчезли, и проверить возврат строки в их функциях.
- Развести тяжёлые задачи по времени, чтобы очередь не упиралась в один агент.
Код
Читаем очередь агентов:
$res = CAgent::GetList(['NEXT_EXEC' => 'ASC'], []);while ($row = $res->Fetch()) { printf("%-40s %s интервал=%s период=%s активен=%s\n", $row['NAME'], $row['NEXT_EXEC'], $row['AGENT_INTERVAL'], $row['IS_PERIOD'], $row['ACTIVE']);}// давнее время следующего запуска означает стоящую очередь, а не поломку агента// ACTIVE = N встречается после ручного отключения агента в административной частиИмя агента в очереди - это буквально строка вызова его кода. По ней агент и удаляется, поэтому регистрация и удаление обязаны совпадать до последнего символа.
Время следующего запуска остаётся главным показателем здоровья всей очереди агентов проекта. Если оно в прошлом у половины строк, разбирают не сами агенты, а способ их выполнения на проекте.
Регистрируем агент с понятным расписанием:
CAgent::AddAgent('\Local\Job\Sync::run();', 'main', 'N', 3600, '', 'Y', ConvertTimeStamp(time() + 60, 'FULL'));// третий аргумент - периодичность: N считает паузу от окончания прошлого запуска// пятый и шестой - проверка даты и активность, седьмой - время первого запускаВозвращаем строку перезапуска:
public static function run(): string{ self::doWork(); return '\Local\Job\Sync::run();'; // без этой строки агент исчезнет после первого раза}// пустая строка вместо вызова - это осознанное самоудаление агента// исключение внутри метода оборвёт разбор очереди на этом же местеПересоздаём агент правильно:
CAgent::RemoveAgent('\Local\Job\Sync::run();', 'main'); // параметры совпадают с регистрациейCAgent::AddAgent('\Local\Job\Sync::run();', 'main', 'N', 900);// повторное добавление без удаления даёт два агента с одинаковым вызовом// имя модуля в удалении обязано совпадать с именем при регистрации агентаПереводим очередь на расписание сервера:
\Bitrix\Main\Config\Option::set('main', 'agents_use_crontab', 'N');\Bitrix\Main\Config\Option::set('main', 'check_agents', 'N'); // хитовый запуск выключен// дальше очередь разбирает отдельный скрипт, который зовёт CAgent::CheckAgents()// в файле dbconn.php при этом объявляют константу поддержки запуска из консолиНастройки выключают только запуск на хитах. Пока планировщик не зовёт разбор очереди своим скриптом, агенты не выполняются вообще, и это самая частая ошибка перевода.
Смотрим строку планировщика:
crontab -l | grep -i cron_events*/1 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php >/dev/null 2>&1# консольный PHP берут тот же, что и у веб-сервера, иначе разойдутся права на кэшМинутный интервал планировщика не означает минутной точности расписания агентов. Каждый запуск разбирает очередь целиком, и агент со своим интервалом всё равно дождётся собственного времени следующего запуска.
Отделяем разовую работу от расписания:
\Bitrix\Main\Application::getInstance()->addBackgroundJob( [\Local\Job\Report::class, 'build'], [$orderId]);// фоновая задача выполнится один раз после ответа и расписания не имеетРазделение простое и работает почти всегда. Разовая доработка после ответа принадлежит фоновой задаче, а всё повторяющееся по времени остаётся за агентом с понятным интервалом.
Ограничения
Агент не имеет ни пользовательского контекста, ни привычного окружения запроса. Проверки прав и обращения к текущему пользователю внутри него бессмысленны и дают разный результат от запуска к запуску.
Долгий агент держит всю очередь платформы на себе до своего завершения. Пока он работает, остальные ждут, а через десять минут платформа считает его зависшим и блокирует повторный запуск.
Периодический режим расписания догоняет все пропущенные слоты подряд без пауз. На редко посещаемом сайте это оборачивается пачкой запусков подряд, а не аккуратным расписанием.
Хитовый запуск очереди полностью зависит от текущей посещаемости конкретного сайта. Ночная выгрузка, поставленная агентом на три часа, выполнится в момент первого утреннего визита.
Фоновая задача выполняется без всяких гарантий: аварийное завершение запроса её теряет. При аварийном завершении запроса она пропадает, поэтому критичные операции ставят агентом или очередью.
Типичные проблемы
Агент выполнился один раз и пропал из списка.
Функция не вернула строку своего следующего вызова, и платформа удалила агента. Возврат строки вызова обязателен для любого агента, который должен повторяться дальше.
В очереди два одинаковых агента.
Агент добавили повторно, не удалив прежнюю регистрацию с теми же параметрами. Пересоздание агента всегда идёт парой: сначала удаление прежнего, а потом добавление нового.
После перевода на расписание агенты встали совсем.
Хитовый запуск выключен настройками, а задание планировщика не создано или зовёт другой файл. Проверяют строку планировщика и путь к тому скрипту, который разбирает очередь.
Один посетитель ждёт страницу несколько секунд.
На его хите пролог страницы отрабатывает подряд все накопившиеся периодические агенты. Тяжёлые задачи переводят на расписание сервера, а периодичность у них отключают.
Часть агентов не выполняется без видимой причины.
Первый в очереди агент падает с ошибкой и обрывает разбор остальных. Ошибку ищут в журнале, а тяжёлую логику оборачивают в перехват исключений.
Агент работает не от того пользователя.
В агенте нет пользовательского контекста, а код рассчитывает на текущего пользователя. Идентификатор нужного пользователя передают прямо в параметрах вызова самого агента.
Частые вопросы
Чем интервал отличается от периодичности?
Интервал отсчитывается от окончания прошлого запуска, периодичность держится жёсткого расписания. Второй режим догоняет пропущенные слоты и на редких хитах даёт пачку запусков.
Почему агент исчез из списка?
Его функция не вернула строку следующего вызова, и платформа считает работу законченной. Возврат пустой строки означает то же самое, но осознанно.
Обязательно ли переводить агенты на расписание?
Для тяжёлых и чувствительных ко времени задач - да, иначе они зависят от посещаемости. Лёгкие агенты спокойно живут на хитах и ничего не ломают.
Можно ли запускать агенты параллельно?
Нет, очередь однопоточная, и долгий агент задерживает остальных. Длинную работу разбивают на порции и обрабатывают по шагам.
Когда брать фоновую задачу вместо агента?
Когда работу нужно доделать один раз сразу после ответа посетителю. Регулярные задачи по расписанию остаются за агентами.
Смежное
- cron и агенты - оглавление подтемы
- Свой агент и задание по расписанию: создание, шаг, защита - как завести агент на практике
- Агенты не выполняются: агенты на хитах и запуск по расписанию - разбор молчащей очереди
- Долгая операция шагами: порции, состояние, прогресс - как не держать очередь одним агентом
- Очередь сообщений: фоновая обработка задач в ядре - соседний способ отложить работу
- Расписание обменов: частота, окна, конфликты, блокировка - расписание тяжёлых интеграций
- Мониторинг сервера: что смотреть до того, как сайт упадёт - как заметить вставшую очередь
- События, агенты и бизнес-процессы - соседние механизмы автоматизации
- Инфраструктура и эксплуатация - устройство серверной части целиком