← Назад к справке

Администрирование

Cron и агенты

Рабочая справка по агентам и cron в коробочном Bitrix24: CAgent::AddAgent, RemoveAgent, CheckAgents, периодичность, cron_events.php и диагностика.

Агент — это отложенный PHP-вызов, который Битрикс запускает по расписанию. На боевых коробках агенты часто переводят на cron, чтобы они выполнялись стабильнее и не зависели от посещений сайта.

Общее понимание

Агенты используются для регулярных и отложенных задач внутри Битрикса.

Что такое агент

Агент — это строка PHP-кода, которую Битрикс хранит в таблице агентов и запускает, когда подходит время выполнения.

Обычно агент должен вернуть строку своего следующего вызова. Если агент вернёт пустую строку, он больше не будет планировать следующий запуск.

Ограничение от дублей

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

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

Практический вывод: долгие задачи лучше дробить на части. Агент должен обработать небольшую порцию данных, сохранить прогресс и вернуть строку следующего запуска.

Cron и хиты

Режим Как работает
На хитах Агенты проверяются при посещениях сайта.
На cron Агенты запускаются системным расписанием сервера.

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

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

Периодический и непериодический запуск

У CAgent::AddAgent() параметр period влияет на расчёт следующего запуска.

period Как считать
Y Следующий запуск считается от запланированного времени: предыдущее время запуска + интервал.
N Следующий запуск считается от фактического завершения: время окончания работы + интервал.

Для тяжёлых обработчиков чаще удобнее непериодический режим N, чтобы следующий запуск не планировался поверх долгого предыдущего выполнения.

Работа с агентами

Базовые операции: добавить, удалить, написать функцию агента и проверить список.

Добавить агент

Агент можно добавить через административный интерфейс или программно через CAgent::AddAgent(). Программный вариант удобен при установке модуля.

<?php

use Bitrix\Main\Loader;

Loader::includeModule('main');

const MODULE_ID = 'itsolution.example';

/**
 * Регистрирует агент модуля.
 */
function addExampleAgent(): int
{
    $agent_id = \CAgent::AddAgent(
        '\\Itsolution\\Example\\Agent\\ExampleAgent::run();',
        MODULE_ID,
        'N',
        300,
        '',
        'Y',
        ''
    );

    return (int) $agent_id;
}

$agent_id = addExampleAgent();

echo $agent_id;

Параметры AddAgent

Полный вызов AddAgent() позволяет указать модуль, интервал, активность, сортировку, пользователя и проверку дублей.

<?php

const MODULE_ID = 'itsolution.example';

/**
 * Добавляет агент с основными параметрами.
 */
function addExampleAgentWithParameters(): int
{
    $agent_id = \CAgent::AddAgent(
        '\\Itsolution\\Example\\Agent\\ExampleAgent::run();',
        MODULE_ID,
        'N',
        3600,
        '',
        'Y',
        '',
        100,
        false,
        true
    );

    return (int) $agent_id;
}

$agent_id = addExampleAgentWithParameters();

echo $agent_id;
Параметр Пример Что означает
Функция ExampleAgent::run(); PHP-код, который должен выполниться.
Модуль itsolution.example Модуль, к которому относится агент. Для функции из общего init.php можно оставить пустую строку.
Периодичность N или Y N — следующий запуск после завершения. Y — от планового времени.
Интервал 3600 Интервал между запусками в секундах.
Дата проверки '' Дата, с которой агент нужно проверять.
Активность Y Запускать агент или нет.
Дата следующего запуска '' Когда агент должен выполниться в следующий раз.
Сортировка 100 Чем меньше значение, тем выше агент в списке.
Пользователь false Обычно системный агент выполняется без конкретного пользователя.
Проверка дубля true Не добавлять второй такой же агент повторно.

Где хранить функцию агента

Функция агента должна быть доступна в момент запуска. Есть два нормальных варианта: общий init.php или класс внутри модуля.

Где лежит код Что указывать в агенте
/local/php_interface/init.php Функция вида agentTest();, модуль можно оставить пустым.
/local/modules/vendor.module/include.php Указать ID модуля, чтобы Битрикс подключил модуль перед запуском агента.
/local/modules/vendor.module/lib/Agent/ExampleAgent.php Вызов статического метода, например Vendor\Module\Agent\ExampleAgent::run();.

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

<?php

/**
 * Простой агент из init.php.
 */
function agentTest(): string
{
    // Здесь основной код агента

    return 'agentTest();';
}

Удалить агент

<?php

const MODULE_ID = 'itsolution.example';

/**
 * Удаляет агент модуля.
 */
function removeExampleAgent(): void
{
    \CAgent::RemoveAgent(
        '\\Itsolution\\Example\\Agent\\ExampleAgent::run();',
        MODULE_ID
    );
}

removeExampleAgent();

Удаление агента обычно вызывают при удалении модуля в uninstallEvents() или отдельном методе uninstallAgents().

Функция агента

<?php

namespace Itsolution\Example\Agent;

use Bitrix\Main\Diag\Debug;
use Bitrix\Main\Loader;

class ExampleAgent
{
    /**
     * Выполняет агент и возвращает следующий вызов.
     */
    public static function run(): string
    {
        try {
            Loader::includeModule('crm');

            Debug::dumpToFile(
                [
                    'message' => 'Агент выполнен',
                    'date' => date('Y-m-d H:i:s'),
                ],
                'example_agent',
                '/local/logs/example_agent.log'
            );
        } catch (\Throwable $exception) {
            Debug::dumpToFile(
                [
                    'message' => $exception->getMessage(),
                    'file' => $exception->getFile(),
                    'line' => $exception->getLine(),
                ],
                'example_agent_error',
                '/local/logs/example_agent.log'
            );
        }

        return '\\Itsolution\\Example\\Agent\\ExampleAgent::run();';
    }
}

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

Ограничить количество запусков

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

<?php

/**
 * Выполняет агент ограниченное количество раз.
 */
function runLimitedAgent(int $limit = 5): string
{
    if ($limit <= 0) {
        return '';
    }

    // Здесь основной код агента

    $next_limit = $limit - 1;

    return 'runLimitedAgent(' . $next_limit . ');';
}

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

Изменить интервал из агента

Интервал следующего запуска можно изменить прямо из функции агента через глобальную переменную $GLOBALS['pPERIOD']. Это повлияет на ближайшее планирование после текущего выполнения.

<?php

/**
 * Меняет следующий интервал запуска агента.
 */
function runAgentWithDynamicInterval(): string
{
    $has_heavy_load = false;

    if ($has_heavy_load) {
        $GLOBALS['pPERIOD'] = 3600;
    } else {
        $GLOBALS['pPERIOD'] = 300;
    }

    // Здесь основной код агента

    return 'runAgentWithDynamicInterval();';
}

Если нужно изменить постоянное значение интервала в записи агента, можно обновить сам агент. Следующий запуск при этом может пройти ещё по старому расписанию, а последующие — уже по новому интервалу.

<?php

/**
 * Меняет постоянный интервал агента.
 */
function updateAgentInterval(int $agent_id, int $interval): bool
{
    if ($agent_id <= 0 || $interval <= 0) {
        return false;
    }

    return (bool) \CAgent::Update(
        $agent_id,
        [
            'AGENT_INTERVAL' => $interval,
        ]
    );
}

$is_updated = updateAgentInterval($agent_id, 3600);

var_dump($is_updated);

Проверить агенты в базе

<?php

use Bitrix\Main\Application;

/**
 * Получает агенты модуля.
 */
function fetchModuleAgents(string $module_id): array
{
    $connection = Application::getConnection();
    $sql_helper = $connection->getSqlHelper();

    $safe_module_id = $sql_helper->forSql($module_id);

    $agents = [];

    $result = $connection->query("
        SELECT
            ID,
            MODULE_ID,
            NAME,
            ACTIVE,
            AGENT_INTERVAL,
            NEXT_EXEC,
            LAST_EXEC,
            RUNNING,
            RETRY_COUNT
        FROM b_agent
        WHERE MODULE_ID = '{$safe_module_id}'
        ORDER BY ID ASC
    ");

    while ($agent = $result->fetch()) {
        $agents[] = $agent;
    }

    return $agents;
}

$agents = fetchModuleAgents('itsolution.example');

print_r($agents);

Если агент выполняется без ошибок и зависаний, в списке должна обновляться дата последнего и следующего запуска. Если растёт количество попыток или агент зависает, нужно смотреть лог функции агента.

Cron

Команда cron зависит от окружения, версии PHP и пути до проекта.

Пример команды cron

Типовая команда для BitrixVM-подобного окружения:

*/1 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php

Добавлять её лучше в crontab пользователя, от которого работает проект, например bitrix.

crontab -u bitrix -e

Частичный перевод на cron

При частичном переводе часть логики остаётся на хитах, а часть выполняется через cron. Система продолжит запускать агенты при посещениях, а cron будет запускать свою часть фоновых задач.

<?php

/**
 * Включает режим частичного использования cron для агентов.
 */
function enablePartialAgentCron(): void
{
    COption::SetOptionString('main', 'agents_use_crontab', 'Y');
}

enablePartialAgentCron();

echo COption::GetOptionString('main', 'agents_use_crontab', 'N');

После этого нужно добавить задание cron на сервере. Путь до сайта заменить на свой.

*/10 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/modules/main/tools/cron_events.php

Полный перевод на cron

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

<?php

/**
 * Отключает проверку агентов на хитах.
 */
function disableHitAgentCheck(): void
{
    COption::SetOptionString('main', 'agents_use_crontab', 'N');
    COption::SetOptionString('main', 'check_agents', 'N');
}

disableHitAgentCheck();

echo COption::GetOptionString('main', 'agents_use_crontab', 'N');
echo COption::GetOptionString('main', 'check_agents', 'Y');

В /bitrix/php_interface/dbconn.php убирают прямое определение BX_CRONTAB_SUPPORT и BX_CRONTAB, а вместо него оставляют условие:

if (!(defined('CHK_EVENT') && CHK_EVENT === true)) {
    define('BX_CRONTAB_SUPPORT', true);
}

Затем создают файл /bitrix/php_interface/cron_events.php.

<?php

$_SERVER['DOCUMENT_ROOT'] = realpath(dirname(__FILE__) . '/../..');
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];

define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
define('BX_NO_ACCELERATOR_RESET', true);
define('CHK_EVENT', true);
define('BX_WITH_ON_AFTER_EPILOG', true);

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

@set_time_limit(0);
@ignore_user_abort(true);

CAgent::CheckAgents();

define('BX_CRONTAB_SUPPORT', true);
define('BX_CRONTAB', true);

if (CModule::IncludeModule('sender')) {
    \Bitrix\Sender\MailingManager::checkPeriod(false);
    \Bitrix\Sender\MailingManager::checkSend();
}

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/tools/backup.php';

CMain::FinalActions();

После этого файл добавляют в cron. На BitrixVM-подобном окружении команда может выглядеть так:

*/1 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php

Путь /home/bitrix/www нужно заменить на путь к своему сайту. Команду лучше запускать от пользователя, которому принадлежат файлы проекта.

Вернуть запуск на хитах

Если нужно вернуть стандартное поведение, можно удалить или сбросить настройки agents_use_crontab и check_agents, убрать изменения из dbconn.php и очистить управляемый кеш.

<?php

/**
 * Возвращает проверку агентов на хитах.
 */
function enableHitAgentCheck(): void
{
    COption::RemoveOption('main', 'agents_use_crontab');
    COption::RemoveOption('main', 'check_agents');
}

enableHitAgentCheck();

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

Проверка руками

Проверить cron можно запуском команды от пользователя проекта:

sudo -u bitrix /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php

Если команда не выполняется руками, cron тоже нормально работать не будет. Сначала нужно исправить путь к PHP, права пользователя или ошибку в PHP-коде.

Проверить запуск агентов можно и из PHP:

<?php

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

/**
 * Принудительно проверяет агенты.
 */
function runAgentsCheck(): void
{
    \CAgent::CheckAgents();
}

runAgentsCheck();

echo 'Agents checked';

Логирование cron

На время проверки можно направить вывод cron в файл.

*/1 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php >> /home/bitrix/cron_events.log 2>&1

После диагностики лучше не оставлять бесконечно растущий лог без ротации.

Источники