
Самым популярным паттерном проектирования классов безусловно был и остаётся
Singleton — паттерн, который гарантирует уникальность объекта класса в рамках одного процесса.
Но что если возникает необходимость унифицировать сам процесс? В этой статье пойдёт речь о том, в каких задачах это может понадобиться и как этого добиться.
Введение
Есть ряд задач, в которых принципиально важно, чтобы определённый скрипт выполнялся исключительно в единственном варианте т.к. параллельный запуск его несколькими процессами может привести к различного рода накладкам.
Примеров тому можно найти много, но по большей части это будут различного рода фоновые обработчики, как то: фоновые рассыльщики email / sms, утилизаторы кэша, мониторинг активных пользователей, фоновый бэкап и т.п.
Два способа реализации
Есть два основных способа реализации таких задач:
- Писать скрипты, которые будут исключать все возможные накладки их параллельного запуска.
- Использовать систему контроля уникальности запуска скрипта.
Первый вариант зачастую намного сложнее, а порой даже вовсе не реализуем. Второй вариант реализовать кажется просто, нужно всего лишь использовать «контроллер» уникальности процесса. По опыту скажу, что разработчики более склонны к написанию как раз таких вот «контроллеров», но проблема в том, что «контроллеры» эти получаются крайне далеки от совершенства, и это приводит к очень нехорошим последствиям.
ScriptAlone — «контроллер» уникальности процесса
ScriptAlone представляет из себя класс, объекты которого служат «контроллерами» уникальности процесса. Он был неоднократно оптимизирован и доработан, а также на 100% протестирован в боевых условиях, так что я могу с уверенностью рекомендовать вам его использование.
Дабы не быть многословным расписывая все его прелести предлагаю познакомиться с тем как он работает на конкретном примере.
Пример
У вас есть живая очередь писем которые должны стабильно оправляться в кратчайшие сроки.
Требования
- Необходим скрипт, который будет постоянно запущен и каждую секунду проверять наличие в очереди писем на отправку.
- Может быть запущена только одна копия скрипта, чтобы исключить ситуации дублированной отправки писем.
- Если работа скрипта прерывается из-за ошибки, то он должен быть перезапущен как можно скорее.
- Если скрипт подвисает на выполнении одной из итераций, то он должен быть перезапущен как можно скорее.
- Скрипт должен перезапускаться каждые 5-10 часов, чтобы предотвратить возможные утечки памяти. Остановка скрипта в этом случае должна производиться безопасным образом (когда задачи текущей итерации выполнены).
- Необходимо некий индикатор того, что скрипт в данный момент запущен
- Необходимо наличие возможности принудительно остановить выполнение скрипта.
Что необходимо сделать
- Взять example.php — простенький скрипт использующий ScriptAlone.
- Настроить CRON на вашем сервере таким образом, чтобы example.php вызывался каждые 5 минут.
Что получится
- Скрипт будет постоянно запущен. Проверка наличия новых писем будет производиться каждую секунду.
- В любой момент времени будет запущена только одна копия скрипта.
- Если работа скрипта прервётся или лимит времени на выполнение одной итерации будет исчерпан, то скрипт будет перезапущен.
- Каждые 5 часов скрипт будет перезапускаться безопасным образом.
- Перезагрузка скрипта подразумевает его повторный запуск не позднее чем через 5 минут после остановки — это обусловлено настройкой CRON-а на попытку запустить очередную копию скрипта каждые 5 минут.
- Убедить в том, что скрипт в данный момент запущен можно по наличию файла ./example.php.works. Также дополнительную информацию можно почерпнуть из его содержимого:
PID: 12656471471193231407
Started: 2010-02-08 19:39:07
Worked: 2010-02-08 19:39:10
Expire: 2010-02-08 19:39:26
- Остановить выполнение скрипта можно просто создав файл с именем ./example.php.works.stop
Пользуйтесь на здоровье :)
Скачать последнюю версию,
подписаться на обновления по RSS или
поучаствовать в разработке можно на
странице проекта.
Спасибо заранее за конструктивную критику и отзывы :)
P.S. В процессе написания статьи было выявлено странное поведение хабра: все слова начинающиеся на Script* переводятся в нижний регистр — исправляется самостоятельной заменой Script на Script. Все ссылки, в которых встречается *script* заменяются на *script* что делает их нерабочими — исправляется самостоятельной заменой *script* на *s%63ript*. Отписался в саппорт, но пока не ответили.
комментарии (63)
Не совсем правильно. В некоторых языках можно изолировать области и создавать по синглтону в каждой такой области. В .NET это AppDomain, в java достигается при помощи собственного class-loader-а.
P.S. А вот в саппорт вы зря отписали, евпочя ;)
А вы то чего жалуетесь? Это ведь ваш первый коммент в этом посте… или это такой новый тип разводки на плюсики?)))
То ли ie7 проглючило, то ли Хабр виноват. Еще раз извините.
А как же мютексы?
Подозреваю, что это можно реализовать только на сервере, к которому у вас есть полный доступ. Сомневаюсь, что на том же VPS хостинге такое получится.
При использовании pcntl вы избавляетесь от необходимости создавать статус-файлы, но зато появляется требование в наличии доступа к kill, что крайне непопулярно.
Плюс ко всему вам всё равно писать «велосипед», чтобы реализовать следующее:
P.S. Но так или иначе, все описанные действия требуют полноценного root-доступа к системе, что в наше время крайне редкостная роскошь.
Апач это апач, он в единственном числе всегда. А как вы будете различать разные скрипты, запушенные под одним процессом(php в частности)?
Сейчас любопытства ради ещё раз заглянул в /var/run своего сервера, и вот о трёх работающих в данный момент php-скриптах вообще никакой информации не нашёл. Вы сами попробуйте запустить парочку параллельных живых скриптов и заглянуть в ту папочку, они там себя никак не обозначают.
А может сначала посмотреть как запускается апач? Или попробовать запустить апач при запущенном его экземпляре, или остановить при остановленном?
Что за дикое нежелание изучать?
Для стоп-файлов конвенции не встречал :-)
Удалять pid-файл для остановки процесса неправильно т.к. в нашем случае он будет повторно запущен CRON-ом в ближайшие 5 минут. Таким образом, чтобы заблокировать выполнение скрипта на сутки придётся каждые 5мин удалять .pid файлы.
Вот поэтому, чтобы не дёргаться с удалением pid-файлов и создаётся один стоп-файл, который и блокирует выполнение скрипта на всё время своего существования.
Тогда читайте: en.wikipedia.org/wiki/Process_identifier
«In computing, the process identifier (normally referred to as the process ID or just PID) is a number used by some operating system kernels (such as that of UNIX, Mac OS X or Microsoft Windows) to uniquely identify a process. This number may be used as a parameter in various function calls allowing processes to be manipulated, such as adjusting the process's priority or killing it altogether.»
Вы читайте внимательней, что я пишу:
Никто и не предлагает удалять pid-файл для остановки процесса — это у вас свои тараканы. Процессу надо слать сигналы! А процесс должен как полагается обработать сигнал, и, если пришел сигнал завершиться, то завершается, завершив все свои неотложные дела. Все ж таки ж не почитали про сигналы…
А вы о чём?
И насчёт слать сигналы я вам уже объяснил, что это не универсальное решение т.к. актуально только на серверах к которым есть root-доступ.
ru.php.net/manual/en/function.posix-kill.php
А если у php прав нет на обработку этих сигналов?
Ну вы напишите класс, который будет через pcntl работать и сравним какому проценту пользователей хабра хватит прав его хотя бы запустить на своих проектах.
pid есть просто идентификация процесса, ассоциативная связь между его названием и уникальным идентификатором под которым он в данный момент в системе запущен — понимаете?
Вы зациклены на линухе и всё прочем. А что если сервер под виндовс? А под виндовс кстати может хоть 10 процессов одной и той же программы быть запущено и у каждой будет свой pid.
В общем не надо понятия каверкать.
Класс написан в первую очередь как универсальное решения. Если бы это было что-то кастомное, заточенное под unix*, под разработчиков с опытом администрирования и root-правами, то это было бы уже совсем другая статья и возможно статья нафиг никому не нужная!
Угу, а под *nix'ами? У всех один? :D
Ну, ей-богу… Окститесь и почитайте… Каждый программер, имхо, хоть раз в жизни, да писал демоны, и вам тоже нужно пройти этот этап. Не воспринимайте все в штыки, просто поизучайте…
Более того, даже бог с ним, оставляйте свой «стоп файл», но теперь-то прочитав про pid-файлы, уберите свои убогие .works файлы, и используйте нормальные pid-файлы. Информация в них все равно лишняя — время запуска скрипта определяется по дате создания pid-файла.
И да, если демон подвис, тогда ждать когда он превысит лимиты времени на каждую итерацию, что в контексте «примера» — всего 16 секунд.
И если я реализовал бы всё на .pid файлах, то 95% разработчиков просто не смогли бы её использовать т.к. статистически root-доступом в наше время располагает крайне маленький процент.
2) откуда такая замечательная статистика про 95%?
3) откуда такая уверенность, что нужны права root'а для kill'a? «Я тебя породил», значит я тебя могу и убить.
4) да хотя бы свои замечательные .works убрали бы.
2) От хостинг-провайдера, и соотношения между VDS/Colocation аккаунтами и VPS.
3) Ага, а сам вызов kill вы каким местом будете делать, когда у вас даже на это прав нету?
4) Лично мне они не мешают, я по крайней мере могу хранить их в тех папках, на которые у меня права записи есть
Затем, если вы все также упорны в своем желании дайте мне VPS, где я якобы могу плодить демоны и не могу их убивать и я напишу. И заодно напишите, чем отблагодарите кроме очередных малодушных претенциозных минусов.
Я действительно только вчера узнал, что VDS и VPS это разные вещи. Думал, что VPS это virtual shared хостинг.
В конце концов VDS — virtual dedicated server — дэдик по нашему. А VPS я думал — противоположное ему т.к. нафига одно и то же двумя разными словами называть?))
А насчёт «идентификации процесса по имени скрипта» какие притензии? У моих процессов есть PID, см.
см. текст Примера в статье:
И кстати, в /var/run сервера имена .pid файлов тоже не случайный хеш, а название процесса. Так вы уже и на линукс-разработчиков наезжаете, что они .pid файлы так соответственно называют?
Не нужны мне .pid-файлы и pcntl т.к. не собираюсь я позволять прочим (не-php) процессам мои псевдо-демоны стопорить.
Ну короче не пристало мне грабли вместо расчёски использовать.
Я не хуже вашего понимаю и могу реализовать эту библиотеку на .pid & pcntl, но я не хочу этого делать, мне это не надо, это не даёт ничего кроме как дополнительного ограничение на использование её только под теми серверами, под которыми у меня есть root-доступ.
Вам хочется на .pid & pcntl всё переписать — перепишите :)
А вас потом попросят на VPS-хостинге что-нибудь аналогичное сделать и вы поднимете этот топик и скачаете scriptAlone))))
Возможно не я один тут с вами не согласен.
Меня вообще честно говоря больше интересует статистика скачиваний библиотеки, чем ваши комментарии. Для меня эта статистика более показательный фактор того, что библиотека вполне адекватна.
Всё я понимаю. Вы мне что предлагаете экстеншены использовать, системные вызовы на уровне ОС? А представьте, что у меня нет такой возможности. Что у меня VPS хостинг и прав нет на такие выкрунтасы.
Вы напишите альтренативный вариант на pcntl и выложите, посмотрим какой процент людей его использовать возьмётся, когда у них прав доступа не хватит, даже чтобы example вашей библиотеки запустить.
Каждый VPS имеет собственную копию системы, с правами доступа уровня root (UID: 0) для Unix или Administrator для Windows, что позволяет производить компиляцию, установку собственного программного обеспечения с изменённой конфигурацией.
Я то думал, что VPS это сокращённое название virtual-shared хостинга! Ну, теперь увы комменты не подправить… так что просто учитывайте где я писал про VPS имелось в виду shared virtual хостинг.
Спасибо за поправку!
Кроме того, у файла появлялись дата и время создания. И можно было видеть, что если файл существует дольше, чем таймлимит, то скрипт вылетел (при правильной работе в конце скрипта сигнальный файл удалялся).
Перезапуск каждые 5-10 часов сделать несложно — при каждой итерации проверять, сколько натикало, да завершать скрипт. Но, поскольку они и так по таймлимиту завершались, то такой необходимости тоже не было.
Принудительно остановить — да, не делал такого, тоже не было необходимости.
Из требований к моему скрипту: работа на шаред-хостингах, где не всегда есть права на запись (разные хостинги, разные юзеры), но есть мускуль (вордпрессовский плагин писал), а еще есть таймлимит.
Это версия плагина Parasie Eliminator, которую я сейчас переписываю. Та надо проверять базу каментов по базе спамерских урлов и сигнатур. Базы того и другого могут быть большими весьма, а хостинги — шареды. Вот базу надо проверять по кускам, при этом не допустить перерасхода ресурсов шареда.
Для этого и делал свой костыль :)
Но странно, что это за shared-хостинг такой, который прав на запись не даёт. Даже не знал, что такие бывают.
На самом деле, лучше чтобы скрипт завершался в перерывах между итерациями, а не когда у него time_limit закончится. А то представьте если у вас не транзакционная БД и скрипт остановится в промежутке между выполнением двух запросов (UPDATE / INSERT) одного сценария. Целостность БД может пострадать и сайт перестанет работать.
Чтобы не страдала БД, я скрипт заставил работать небольшими кусочками.
Была мысль получать значение таймлимита, затем определять, сколько уже прошло времени и, исходя из этого, брать размер задачи (проверка идет в то время, которое остается от таймлимита после окончания формирования основной части страницы блога для пользователя).