Запуск только одной копии скрипта
Есть консольный скрипт. Скрипт получает набор данных, обрабатывает, записывает результат в БД, отмечает статус выполнения. Если этот скрипт запустить с небольшим интервалом два и более раз, то возможна ситуация, при которой запущенные в разное время скрипты могут обрабатывать одни и те же данные.
Нечто подобное происходило в моей системе. По крону был назначен запуск скрипта с интервалом в 5 минут. Скрипт получал список из 300 URL, скачивал страницы и обрабатывал их. В случае недоступности удаленного хоста, глюков самого скрипта или солнечных бурь, происходило «наступание на хвост». В итоге я уменьшил таймаут ожидания скачивания страницы до 5 секунд, а количество выдаваемых URL до 100. В итоге за час стало обрабатываться не более 1200 страниц.
Но ведь проблему можно решить и по-другому. Задача ведь проста – не дать запуститься двум копиям скрипта. И сделаем мы это с помощью системы блокировок временного файла.
Алгоритм:
- Проверяем, заблокирован ли файл по указанному адресу
- Если файл заблокирован – die
- Иначе – записываем в него полезную информацию (например, текущую метку времени)
- Выполняем необходимые операции
- При завершении скрипта – закрываем файл
В итоге при запуске скрипта, если запущена единственная копия, он будет работать как обычно. Если одновременно запустить еще одну копию, он сразу же завершит свою работу.
Код примера:
<?php $begin_time = time(); $lock_file = __DIR__.'/lock_test'; $fp = fopen($lock_file, "w+"); // регистрируем функцию shutdown, которая будет выполнена при завершении скрипта register_shutdown_function('shutdown', &$begin_time, &$fp); // проверяем статус блокировки if (!flock($fp, LOCK_EX | LOCK_NB)) { die("another script running"); } fwrite($fp, date('Y-m-d H:i:s')); echo "running" . PHP_EOL; //бесконечный цикл while (1 == 1) { // выполняем полезные действия process(); // ограничиваем выполнение одной минутой if (time() >= $begin_time+60) { die; } } // для примера - просто засыпаем на 1 секунду function process() { sleep(1); } // в экстренном случае мы должны закрыть файл function shutdown($begin_time, $fp) { echo "total time: " . ( time()-$begin_time ) . PHP_EOL; fclose($fp); } |
Пример запуска. Открываем две консоли. В первой консоли (после запуска скрипт работает 60 секунд):
# php test.php
running
total time: 60
Во второй консоли (пока не прошли 60 секунд):
# php test.php
another script running total time: 0
# php test.php
another script running total time: 0
# php test.php
running
Поясню. В первой консоли мы запустили скрипт, который успешно начал выполнять свои задачи. Сразу после этого переключаемся на вторую консоль и пробуем запустить там тот же самый скрипт. В итоге скрипт моментально завершает работу (время выполнения 0 секунд). Ждем минуту и пробуем во второй консоли запустить тот же самый скрипт (вдруг баг консоли или еще что-то не предвиденное). И видим сообщение о том, что скрипт нормально запустился. Задача выполнена.
Чуть подробнее о моем реальном примере. Скрипт, о котором я говорил в начале статьи, успешно переписан. За основу взят пример из статьи, с той лишь разницей, что ограничение на выполнение увеличено до 59 минут ($begin_time+60*59
). Скрипт непрерывно опрашивает базу данных, получает задачи и выполняет их. Отработав 59 минут он завершается. Cron настроен на ежеминутное выполнение этого скрипта. В итоге:
– Если все работает без ошибок: скрипт отработает 59 минут, обработает за это время до 3540 страниц и успешно завершится. Все эти 59 минут другие копии не смогут запускаться.
– Если скрипт умирает с ошибкой: происходит остановка в момент ошибки. В следующую минуту новая копия скрипта запустится и будет пытаться выполнять задачи.
Подобная схема позволяет нам не беспокоиться об утечках памяти, возможных крахах скрипта и прочих форс-мажорных ситуациях.
Можно хитрее, организовать очередь через СУБД.
Параллельно работает несколько скриптов(неограниченное количество), конфликтов доступа нет. алгоритм:
для очереди в реляционной субд(нужно придумать уникальный id для каждого запуска скрипта):
update queue set owner = id limit 1;
select * from queue where owner = id;
@IAD
Все зависит от задачи.
К примеру надо мониторить удаленные ресурсы. А для этого скачивать состояние с определенной страницы. Тогда физически нельзя допускать одновременной работы двух скриптов.
Один раз я делал примерно как описали вы. Задача была — скачать 1200000 страниц с ограничением в 1сек/запрос с одного IP. Что бы ускорить процесс — запустил в 10 потоков, каждый через свою прокси. Задачи распределил между ними, исходя из остатка от деления на 10.
То есть все id с последней цифрой 1 — получил первый скрипт.
С окончанием 2 — второй скрипт. И так далее.
Зачем использовать register_shutdown_function, если lock-файл в любом случае автоматически закроется при завершении скрипта?
@pumbo
Спасибо, не знал.
К тому же привык действовать надежно: die в конце скриптов, очистка переменных после использования, принудительное закрытие файла и так далее.
Спасибо за информацию, вроде рабочий метод!