Принцип работы
Системный cron запускает PHP‑скрипт по расписанию (например, раз в минуту или в конкретное время суток). Скрипт:
- Проверяет, нужно ли выполнить какую‑либо задачу в текущий момент.
- Выполняет задачи, соответствующие текущему времени.
- Логирует действия.
- Завершает работу.
Такой подход исключает необходимость бесконечных циклов и пауз в PHP‑коде.
Шаг 1. Подготовка окружения
Убедитесь, что:
- на сервере установлен PHP и доступен из командной строки (
php -v); - есть доступ к редактированию cron‑задач (
crontab -e); - у скрипта есть права на запись в директорию для логов.
Шаг 2. Создание PHP‑скрипта планировщика
Создайте файл "task_scheduler.php" со следующим содержимым:
ВыделитьPHP
<?php
// Файл task_scheduler.php
// Конфигурация
$logFile = __DIR__ . '/scheduler.log';
$lockFile = __DIR__ . '/scheduler.lock';
/**
* Записывает сообщение в лог с временной меткой
*/
function logMessage($message) {
global $logFile;
$timestamp = date('Y-m-d H:i:s');
file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND);
}
/**
* Проверяет, не запущен ли уже экземпляр скрипта
*/
function isAlreadyRunning() {
global $lockFile;
if (file_exists($lockFile)) {
$pid = (int)file_get_contents($lockFile);
// Проверяем, жив ли процесс с этим PID
if (posix_kill($pid, 0)) {
return true;
}
// Если процесс мёртв, удаляем старый lock‑файл
unlink($lockFile);
}
return false;
}
/**
* Создаёт lock‑файл для защиты от параллельного запуска
*/
function createLock() {
global $lockFile;
file_put_contents($lockFile, getmypid());
}
/**
* Удаляет lock‑файл после завершения работы
*/
function removeLock() {
global $lockFile;
if (file_exists($lockFile)) {
unlink($lockFile);
}
}
// Защита от параллельного запуска
if (isAlreadyRunning()) {
logMessage("Планировщик уже запущен. Выход.");
exit(1);
}
createLock();
// Список задач с cron‑расписанием
$tasks = [
[
'task' => function() {
logMessage("Выполняется очистка кэша...");
// Логика очистки кэша
// Например: удаление старых файлов
logMessage("Очистка кэша завершена.");
},
'schedule' => '0 0 * * *', // ежедневно в 00:00
'description' => 'Очистка кэша'
],
[
'task' => function() {
logMessage("Выполняется рассылка уведомлений...");
// Логика рассылки уведомлений
logMessage("Рассылка уведомлений завершена.");
},
'schedule' => '0 9 * * 0', // каждое воскресенье в 09:00
'description' => 'Рассылка уведомлений'
]
];
/**
* Проверяет, нужно ли выполнить задачу по cron‑расписанию
*/
function shouldRun($cronExpression) {
$parts = explode(' ', $cronExpression);
if (count($parts) !== 5) {
return false;
}
$minute = (int)date('i');
$hour = (int)date('H');
$dayOfMonth = (int)date('j');
$month = (int)date('n');
$dayOfWeek = (int)date('w'); // 0 — воскресенье, 6 — суббота
return matchCronPart($parts[0], $minute) &&
matchCronPart($parts[1], $hour) &&
matchCronPart($parts[2], $dayOfMonth) &&
matchCronPart($parts[3], $month) &&
matchCronPart($parts[4], $dayOfWeek);
}
/**
* Сравнивает часть cron‑выражения с текущим значением
*/
function matchCronPart($cronPart, $currentValue) {
if ($cronPart === '*') {
return true;
}
if (str_contains($cronPart, ',')) {
$values = array_map('intval', explode(',', $cronPart));
return in_array($currentValue, $values);
}
return (int)$cronPart === $currentValue;
}
// Основной блок выполнения
try {
logMessage("Запуск планировщика задач.");
foreach ($tasks as $task) {
if (shouldRun($task['schedule'])) {
logMessage("Запуск задачи: {$task['description']}");
$task['task']();
}
}
logMessage("Завершение работы планировщика.");
} catch (Exception $e) {
logMessage("Ошибка: " . $e->getMessage());
} finally {
removeLock();
}Шаг 3. Настройка системного cron
Настройте системный планировщик для запуска скрипта. Откройте редактор cron:
ВыделитьBash
crontab -eДобавьте строку для запуска скрипта каждую минуту:
ВыделитьBash
* * * * * /usr/bin/php /path/to/task_scheduler.phpГде:
/usr/bin/php— путь к интерпретатору PHP (проверьте черезwhich php);/path/to/task_scheduler.php— полный путь к вашему скрипту.
Шаг 4. Проверка работы
Запустите скрипт вручную для проверки:
ВыделитьBash
php /path/to/task_scheduler.phpПроверьте лог на наличие записей:
ВыделитьBash
tail -f scheduler.logУбедитесь, что lock‑файл создаётся и удаляется корректно.
Дополнительные рекомендации
- Ротация логов. Настройте logrotate для автоматического архивирования и очистки логов.
- Обработка ошибок. Добавьте try‑catch для каждой задачи отдельно, чтобы ошибка в одной не останавливала остальные.
- Таймауты. Установите лимит времени выполнения скрипта (
set_time_limit(30)), чтобы избежать зависаний. - Уведомления о сбоях. Настройте отправку email или сообщений в мессенджер при критических ошибках.
- Тестирование. Протестируйте расписание на тестовых задачах перед внедрением в продакшен.
- Масштабирование. Для большого числа задач рассмотрите использование очередей (Redis, RabbitMQ) и фоновых воркеры.
Преимущества подхода
- Эффективность. Скрипт запускается только когда нужно, не потребляя ресурсы в простое.
- Точность. Задачи выполняются строго по расписанию системного cron.
- Надёжность. Защита от параллельных запусков предотвращает конфликты.
- Прозрачность. Логирование фиксирует все действия и ошибки.
- Масштабируемость. Легко добавлять новые задачи и менять расписание.