Настройка планировщика задач (Cron-like scheduler) для фоновых процессов

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка планировщика задач (Cron-like scheduler) для фоновых процессов
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Настройка планировщика задач (Cron-like scheduler) для фоновых процессов

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

Laravel Task Scheduler

Принцип работы: одна запись в системном cron вызывает планировщик каждую минуту, а он сам решает, какие задачи запустить:

# /etc/cron.d/laravel
* * * * * www-data php /var/www/artisan schedule:run >> /dev/null 2>&1

Все расписания определяются в routes/console.php (Laravel 9+) или app/Console/Kernel.php:

// routes/console.php
use Illuminate\Support\Facades\Schedule;

// Артисан-команды
Schedule::command('reports:daily')->dailyAt('02:00');
Schedule::command('sitemap:generate')->hourly();
Schedule::command('cache:clear-expired')->everyFifteenMinutes();

// Диспатч Queue Job
Schedule::job(new CleanupOldUploadsJob())->weekly()->sundays()->at('03:00');
Schedule::job(new SyncExchangeRatesJob(), 'high')->everyThirtyMinutes();

// Произвольный код
Schedule::call(function () {
    DB::table('sessions')->where('last_activity', '<', now()->subDays(30))->delete();
})->daily()->name('cleanup-sessions')->withoutOverlapping();

// Shell-команда
Schedule::exec('node scripts/process-queue.js')->everyFiveMinutes();

Важные модификаторы

withoutOverlapping() — не запускать задачу, если предыдущий запуск ещё не завершился. Критично для длительных задач:

Schedule::command('import:products')
    ->hourly()
    ->withoutOverlapping(10); // lock на 10 минут

runInBackground() — не ждать завершения команды перед следующей. Scheduler продолжает работу, пока задача выполняется в отдельном процессе:

Schedule::command('reports:generate')->daily()->runInBackground();

onOneServer() — при нескольких серверах выполнять задачу только на одном. Требует cache-драйвер с поддержкой атомарных блокировок (Redis, Memcached):

Schedule::command('newsletter:send')
    ->dailyAt('09:00')
    ->onOneServer()
    ->withoutOverlapping();

between() — ограничить временно́й диапазон:

Schedule::command('process:orders')
    ->everyMinute()
    ->between('08:00', '22:00'); // только в рабочие часы

when() / skip() — условный запуск:

Schedule::command('sync:users')
    ->hourly()
    ->skip(fn() => app()->isDownForMaintenance());

Хранение истории выполнения

По умолчанию Laravel не хранит историю задач. Добавляем через хук onSuccess/onFailure:

Schedule::command('reports:daily')
    ->dailyAt('02:00')
    ->before(function () {
        ScheduleLog::create([
            'command'   => 'reports:daily',
            'status'    => 'started',
            'started_at'=> now(),
        ]);
    })
    ->onSuccess(function (\Illuminate\Foundation\Bus\PendingDispatch $pending) {
        ScheduleLog::where('command', 'reports:daily')
            ->latest()
            ->first()
            ?->update(['status' => 'success', 'finished_at' => now()]);
    })
    ->onFailure(function () {
        ScheduleLog::where('command', 'reports:daily')
            ->latest()
            ->first()
            ?->update(['status' => 'failed', 'finished_at' => now()]);

        Http::post(config('services.slack.webhooks.alerts'), [
            'text' => ":x: Scheduled task `reports:daily` failed",
        ]);
    });

Либо использовать пакет spatie/laravel-schedule-monitor, который делает это автоматически для всех задач и интегрируется с Oh Dear для внешнего мониторинга.

Мониторинг через Healthcheck URL

Паттерн heartbeat: при успешном выполнении задача пингует внешний сервис (Healthchecks.io, Better Uptime, Dead Man's Snitch). Если пинг не пришёл — сервис отправляет алерт:

Schedule::command('backup:run')
    ->daily()
    ->onSuccess(function () {
        Http::get('https://hc-ping.com/' . config('services.healthchecks.backup_uuid'));
    })
    ->onFailure(function () {
        Http::get('https://hc-ping.com/' . config('services.healthchecks.backup_uuid') . '/fail');
    });

Динамические расписания из базы данных

Расписания из конфига — это статика. Если нужно управлять расписаниями через интерфейс (например, у каждого клиента своё время отправки отчёта):

// routes/console.php
use App\Models\ScheduledTask;

ScheduledTask::where('is_active', true)->each(function (ScheduledTask $task) {
    $event = Schedule::call(function () use ($task) {
        dispatch(new DynamicScheduledJob($task->id));
    })
    ->cron($task->cron_expression)
    ->name("dynamic-task-{$task->id}")
    ->withoutOverlapping();

    if ($task->only_on_weekdays) {
        $event->weekdays();
    }
});

Таблица scheduled_tasks:

CREATE TABLE scheduled_tasks (
    id              BIGSERIAL PRIMARY KEY,
    name            VARCHAR(255) NOT NULL,
    cron_expression VARCHAR(100) NOT NULL,  -- '0 9 * * 1-5'
    job_class       VARCHAR(500) NOT NULL,
    payload         JSONB,
    is_active       BOOLEAN DEFAULT true,
    last_run_at     TIMESTAMP WITH TIME ZONE,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Node.js: node-cron / agenda

Для Node.js-сервисов — node-cron (простые задачи) или agenda (с персистентностью в MongoDB):

// node-cron
import cron from 'node-cron';

cron.schedule('0 */2 * * *', async () => {
    console.log('Running every 2 hours');
    await syncExchangeRates();
}, {
    scheduled: true,
    timezone:  'Europe/Kiev',
});
// agenda с MongoDB — история выполнения из коробки
import Agenda from 'agenda';

const agenda = new Agenda({ db: { address: process.env.MONGODB_URI } });

agenda.define('send daily digest', async (job) => {
    await sendDailyDigest(job.attrs.data.userId);
});

await agenda.start();
await agenda.every('24 hours', 'send daily digest', { userId: 123 });

Supervisor для планировщика

В контейнерной среде (Docker) системный cron может быть недоступен или нежелателен. Альтернатива — запускать schedule:work (появился в Laravel 8):

php artisan schedule:work

Это процесс, который сам следит за расписанием без системного cron. В Dockerfile:

CMD ["php", "artisan", "schedule:work"]

Или в Supervisor рядом с queue worker:

[program:scheduler]
command=php /var/www/artisan schedule:work
autostart=true
autorestart=true
user=www-data
stdout_logfile=/var/log/scheduler.log

Сроки

Перевод существующих cron-задач на Laravel Scheduler, базовые модификаторы — 2–3 часа. Хранение истории, алертинг, healthcheck-интеграция — ещё 3–4 часа. Динамические расписания из БД — отдельно, 5–7 часов.