Организация уникальных процессов — ScriptAlone спешит на помощь


    Самым популярным паттерном проектирования классов безусловно был и остаётся Singleton — паттерн, который гарантирует уникальность объекта класса в рамках одного процесса.

    Но что если возникает необходимость унифицировать сам процесс? В этой статье пойдёт речь о том, в каких задачах это может понадобиться и как этого добиться.

    Введение

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

    Примеров тому можно найти много, но по большей части это будут различного рода фоновые обработчики, как то: фоновые рассыльщики email / sms, утилизаторы кэша, мониторинг активных пользователей, фоновый бэкап и т.п.

    Два способа реализации

    Есть два основных способа реализации таких задач:
    1. Писать скрипты, которые будут исключать все возможные накладки их параллельного запуска.
    2. Использовать систему контроля уникальности запуска скрипта.
    Первый вариант зачастую намного сложнее, а порой даже вовсе не реализуем. Второй вариант реализовать кажется просто, нужно всего лишь использовать «контроллер» уникальности процесса. По опыту скажу, что разработчики более склонны к написанию как раз таких вот «контроллеров», но проблема в том, что «контроллеры» эти получаются крайне далеки от совершенства, и это приводит к очень нехорошим последствиям.

    ScriptAlone — «контроллер» уникальности процесса

    ScriptAlone представляет из себя класс, объекты которого служат «контроллерами» уникальности процесса. Он был неоднократно оптимизирован и доработан, а также на 100% протестирован в боевых условиях, так что я могу с уверенностью рекомендовать вам его использование.

    Дабы не быть многословным расписывая все его прелести предлагаю познакомиться с тем как он работает на конкретном примере.

    Пример

    У вас есть живая очередь писем которые должны стабильно оправляться в кратчайшие сроки.

    Требования
    1. Необходим скрипт, который будет постоянно запущен и каждую секунду проверять наличие в очереди писем на отправку.
    2. Может быть запущена только одна копия скрипта, чтобы исключить ситуации дублированной отправки писем.
    3. Если работа скрипта прерывается из-за ошибки, то он должен быть перезапущен как можно скорее.
    4. Если скрипт подвисает на выполнении одной из итераций, то он должен быть перезапущен как можно скорее.
    5. Скрипт должен перезапускаться каждые 5-10 часов, чтобы предотвратить возможные утечки памяти. Остановка скрипта в этом случае должна производиться безопасным образом (когда задачи текущей итерации выполнены).
    6. Необходимо некий индикатор того, что скрипт в данный момент запущен
    7. Необходимо наличие возможности принудительно остановить выполнение скрипта.
    Что необходимо сделать
    1. Взять example.php — простенький скрипт использующий ScriptAlone.
    2. Настроить CRON на вашем сервере таким образом, чтобы example.php вызывался каждые 5 минут.
    Что получится
    1. Скрипт будет постоянно запущен. Проверка наличия новых писем будет производиться каждую секунду.
    2. В любой момент времени будет запущена только одна копия скрипта.
    3. Если работа скрипта прервётся или лимит времени на выполнение одной итерации будет исчерпан, то скрипт будет перезапущен.
    4. Каждые 5 часов скрипт будет перезапускаться безопасным образом.
    5. Перезагрузка скрипта подразумевает его повторный запуск не позднее чем через 5 минут после остановки — это обусловлено настройкой CRON-а на попытку запустить очередную копию скрипта каждые 5 минут.
    6. Убедиться в том, что скрипт в данный момент запущен можно по наличию файла ./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
    7. Остановить выполнение скрипта можно просто создав файл с именем ./example.php.works.stop

    Пользуйтесь на здоровье :)


    Скачать последнюю версию, подписаться на обновления по RSS или поучаствовать в разработке можно на странице проекта.

    Спасибо заранее за конструктивную критику и отзывы :)

    P.S. В процессе написания статьи было выявлено странное поведение хабра: все слова начинающиеся на Script* переводятся в нижний регистр — исправляется самостоятельной заменой Script на Script. Все ссылки, в которых встречается *script* заменяются на *script* что делает их нерабочими — исправляется самостоятельной заменой *script* на *s%63ript*. Отписался в саппорт, но пока не ответили.

    Комментарии 63

      –2
      >> уникальность в рамках одного процесса
      Не совсем правильно. В некоторых языках можно изолировать области и создавать по синглтону в каждой такой области. В .NET это AppDomain, в java достигается при помощи собственного class-loader-а.

      P.S. А вот в саппорт вы зря отписали, евпочя ;)
        0
        Ну поэтому я написал пост исключительно в блог PHP :)
          –1
          Млин, да что ж такое с Хабром стало — ни единого коммента не напишешь, чтобы в карму не заполучить. :(
            0
            Боже упаси, я таким не занимаюсь. Я посты пишу, и вот за этот в частности мне уже «нас#али».

            А вы то чего жалуетесь? Это ведь ваш первый коммент в этом посте… или это такой новый тип разводки на плюсики?)))
          0
          Ой, прошу прощения, я не вам. Хабр что-то глючит и висит, и я отправил не туда
          0
          Синхронизация по именам файлов — брутально.

          А как же мютексы?
            0
            А есть пример реализации, с возможностью запуска на VPS хостинге?
            0
            Зачем велосипед? Обычно создают pid-файлы и сигналы. Создавать в качестве сигнала файл вообще странно.
              0
              А можно подробнее про сигналы?
              Подозреваю, что это можно реализовать только на сервере, к которому у вас есть полный доступ. Сомневаюсь, что на том же VPS хостинге такое получится.
                0
                Не знаю как насчет VPS, но если есть доступ к крону то, наверное, и доступ к kill есть. Посмотрите, man kill и php.net/pcntl
                  0
                  Как я и предполагал, ничего нового вы мне не рассказали.

                  При использовании pcntl вы избавляетесь от необходимости создавать статус-файлы, но зато появляется требование в наличии доступа к kill, что крайне непопулярно.

                  Плюс ко всему вам всё равно писать «велосипед», чтобы реализовать следующее:

                  Если работа скрипта прерывается из-за ошибки, то он должен быть перезапущен как можно скорее.
                  Если скрипт подвисает на выполнении одной из итераций, то он должен быть перезапущен как можно скорее.
                  Скрипт должен перезапускаться каждые 5-10 часов, чтобы предотвратить возможные утечки памяти. Остановка скрипта в этом случае должна производиться безопасным образом (когда задачи текущей итерации выполнены).
                  Необходимо некий индикатор того, что скрипт в данный момент запущен
                  Необходимо наличие возможности принудительно остановить выполнение скрипта.
                    –1
                    Видимо, вы очень мало знакомы с администрированием. В качестве «статус файлов», как вы называете, обычно используются файлы *.pid находящиеся в заранее определенном месте(например, можете посмотреть в /var/run). Скрипты, запускаемые по крону смотрят есть ли этот файл, и проверяют есть ли процесс с данным PID и если есть, то завершаются. «Возможные утечки памяти» это отдельная песня. Обработка сигналов в самом скрипте как раз и позволит завершать задачу, тогда когда это можно. А уж что тривиальнее для принудительной остановки скрипта, чем kill -9 PID.
                      0
                      Что-то мне слабо представляется как это будет реализовано. У меня к примеру по крону запускается 4 разных независимых скрипта (псевдо демона): один шлёт SMS-ки, второй email-ы, третий бэкапит и т.д… Как я в этих скриптах смогу по PID определить, что скрипт с таким же именем уже запущен?

                      P.S. Но так или иначе, все описанные действия требуют полноценного root-доступа к системе, что в наше время крайне редкостная роскошь.
                        0
                        А как апач узнает, что он уже запущен или остановлен? Вы так и не посмотрели в /var/run…
                          –4
                          Я в своё время туда уже насмотрелся, и так знаю что там. Вы так и не ответили на мой вопрос.

                          Апач это апач, он в единственном числе всегда. А как вы будете различать разные скрипты, запушенные под одним процессом(php в частности)?

                          Сейчас любопытства ради ещё раз заглянул в /var/run своего сервера, и вот о трёх работающих в данный момент php-скриптах вообще никакой информации не нашёл. Вы сами попробуйте запустить парочку параллельных живых скриптов и заглянуть в ту папочку, они там себя никак не обозначают.
                            –1
                            Pid-файлы вы сами должны создавать! В крайнем случае сделать ps aux | grep имя_скрипта.
                            Апач это апач, он в единственном числе всегда.

                            А может сначала посмотреть как запускается апач? Или попробовать запустить апач при запущенном его экземпляре, или остановить при остановленном?
                            Что за дикое нежелание изучать?
                  –1
                  И кстати на VPS это получится.
                0
                Обычно для каждого запущенного процесса делают блокирующий файл <кодовое имя>.pid
                Для стоп-файлов конвенции не встречал :-)
                  –2
                  pid-файл в основном используется для сигнализации того, что процесс запущен и у него такой-то уникальный идентификатор, по которому к нему можно обратиться.

                  Удалять pid-файл для остановки процесса неправильно т.к. в нашем случае он будет повторно запущен CRON-ом в ближайшие 5 минут. Таким образом, чтобы заблокировать выполнение скрипта на сутки придётся каждые 5мин удалять .pid файлы.

                  Вот поэтому, чтобы не дёргаться с удалением pid-файлов и создаётся один стоп-файл, который и блокирует выполнение скрипта на всё время своего существования.
                    0
                    Ну что ж такое то… Ну хоть немножко пощупали матчасть бы. Для остановки процесса читают pid-файл и шлют сигнал тому процессу, что нужно остановиться.
                      –1
                      Да что же вы все тут одни такие самые умные?))

                      Тогда читайте: 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-файл для остановки процесса неправильно т.к. в нашем случае он будет повторно запущен CRON-ом в ближайшие 5 минут. Таким образом, чтобы заблокировать выполнение скрипта на сутки придётся каждые 5мин удалять .pid файлы.

                        0
                        Гляжу в книгу…
                        Никто и не предлагает удалять pid-файл для остановки процесса — это у вас свои тараканы. Процессу надо слать сигналы! А процесс должен как полагается обработать сигнал, и, если пришел сигнал завершиться, то завершается, завершив все свои неотложные дела. Все ж таки ж не почитали про сигналы…
                          0
                          Эта ветка комментариев началась с предложения Teapot по поводу: «Обычно для каждого запущенного процесса делают блокирующий файл <кодовое имя>.pid» вот как раз несвязном словосочетании «блокирующий файл <кодовое имя>.pid» я все это время и писал.

                          А вы о чём?

                          И насчёт слать сигналы я вам уже объяснил, что это не универсальное решение т.к. актуально только на серверах к которым есть root-доступ.
                            0
                            Слать сигналы это правильное и стандартное решение.
                            ru.php.net/manual/en/function.posix-kill.php
                              0
                              Ага, а если сервер под виндой, то что делать?
                              А если у php прав нет на обработку этих сигналов?

                              Ну вы напишите класс, который будет через pcntl работать и сравним какому проценту пользователей хабра хватит прав его хотя бы запустить на своих проектах.
                                –2
                                А если… А если… А если под виндой, то лучше уж сервисы использовать.
                              0
                              И блокирующий файл <кодовое имя>.pid, означает блокировку запуска дубликата!
                                –1
                                Ребята из википедии с вами не согласны, читайте ещё раз, только внимательней: en.wikipedia.org/wiki/Process_identifier

                                pid есть просто идентификация процесса, ассоциативная связь между его названием и уникальным идентификатором под которым он в данный момент в системе запущен — понимаете?

                                Вы зациклены на линухе и всё прочем. А что если сервер под виндовс? А под виндовс кстати может хоть 10 процессов одной и той же программы быть запущено и у каждой будет свой pid.

                                В общем не надо понятия каверкать.
                                Класс написан в первую очередь как универсальное решения. Если бы это было что-то кастомное, заточенное под unix*, под разработчиков с опытом администрирования и root-правами, то это было бы уже совсем другая статья и возможно статья нафиг никому не нужная!
                                  0
                                  А под виндовс кстати может хоть 10 процессов одной и той же программы быть запущено и у каждой будет свой pid

                                  Угу, а под *nix'ами? У всех один? :D
                                  Ну, ей-богу… Окститесь и почитайте… Каждый программер, имхо, хоть раз в жизни, да писал демоны, и вам тоже нужно пройти этот этап. Не воспринимайте все в штыки, просто поизучайте…
                                    0
                                    Кстати, в windows есть kill. И под вашу задачу перезапуска скрипта обычно используют kill -HUP. И как же интересно вы будете принудительно стопить зависший демон без килла? Ждать когда лимиты ваш демон превысит?
                                    Более того, даже бог с ним, оставляйте свой «стоп файл», но теперь-то прочитав про pid-файлы, уберите свои убогие .works файлы, и используйте нормальные pid-файлы. Информация в них все равно лишняя — время запуска скрипта определяется по дате создания pid-файла.
                                      0
                                      Принудительно в процессе выполнения нельзя ничего стопить.

                                      И да, если демон подвис, тогда ждать когда он превысит лимиты времени на каждую итерацию, что в контексте «примера» — всего 16 секунд.

                                      И если я реализовал бы всё на .pid файлах, то 95% разработчиков просто не смогли бы её использовать т.к. статистически root-доступом в наше время располагает крайне маленький процент.
                                        0
                                        1) «если демон подвис» он у вас бесконечно будет висеть, до тех пор пока rlimit'ы не превысит, если они установлены.
                                        2) откуда такая замечательная статистика про 95%?
                                        3) откуда такая уверенность, что нужны права root'а для kill'a? «Я тебя породил», значит я тебя могу и убить.
                                        4) да хотя бы свои замечательные .works убрали бы.
                                          0
                                          1) У меня на каждой итерации ставится лимит на процесс через set_time_limit равный максимальному лимиту отведённому на одну итерацию. Вы даже не разобрались как scriptAlone работает.

                                          2) От хостинг-провайдера, и соотношения между VDS/Colocation аккаунтами и VPS.

                                          3) Ага, а сам вызов kill вы каким местом будете делать, когда у вас даже на это прав нету?

                                          4) Лично мне они не мешают, я по крайней мере могу хранить их в тех папках, на которые у меня права записи есть
                                            0
                                            Прочитайте, например, это habrahabr.ru/blogs/php/40432/
                                            Затем, если вы все также упорны в своем желании дайте мне VPS, где я якобы могу плодить демоны и не могу их убивать и я напишу. И заодно напишите, чем отблагодарите кроме очередных малодушных претенциозных минусов.
                                                0
                                                В общем вы так некрасиво признаете свою неправоту. С вами все ясно.
                                                  0
                                                  Слушайте, что вам от меня надо? :)

                                                  Я действительно только вчера узнал, что VDS и VPS это разные вещи. Думал, что VPS это virtual shared хостинг.

                                                  В конце концов VDS — virtual dedicated server — дэдик по нашему. А VPS я думал — противоположное ему т.к. нафига одно и то же двумя разными словами называть?))
                            0
                            И вообще, что за идентификация процесса по имени скрипта? А если один скрипт с разными параметрами? Поизучайте, что такое демоны вообще.
                              0
                              А если один скрипт с разными параметрами, то генерируйте соответствующие уникальные имена state-файлов, это в общем совсем не сложно.

                              А насчёт «идентификации процесса по имени скрипта» какие притензии? У моих процессов есть PID, см.

                                0


                                см. текст Примера в статье:
                                PID: 12656471471193231407
                                Started: 2010-02-08 19:39:07
                                Worked: 2010-02-08 19:39:10
                                Expire: 2010-02-08 19:39:26


                                И кстати, в /var/run сервера имена .pid файлов тоже не случайный хеш, а название процесса. Так вы уже и на линукс-разработчиков наезжаете, что они .pid файлы так соответственно называют?
                                  0
                                  Я удивляюсь… Вы таки удосужились хоть немножко прочитать что такое pid-file? Я вообще-то как раз и советую пользоваться pid-файлами! По-поводу именования же pid-файлов, вам стоит узнать как запускают по два и более экземпляров демонов. Вообще погуглите на слово daemonize.
                                    0
                                    Да знаю я всё это, вот только что вы то не поймёте, что не актуально мне решение работающее только на серверах где root-доступ есть.

                                    Не нужны мне .pid-файлы и pcntl т.к. не собираюсь я позволять прочим (не-php) процессам мои псевдо-демоны стопорить.

                                    Ну короче не пристало мне грабли вместо расчёски использовать.
                                      0
                                      Эхъ… оказывается все демоны грабли…
                                        0
                                        Кстати, спасибо за вознаграждение в виде минусов за информацию.
                                          0
                                          Послушайте, я понимаю, что вам близки админские темы и хотели бы иметь подобное решение реализованное в другом виде. Но в другом виде нет и не будет, просто этот топик не для вас.

                                          Я не хуже вашего понимаю и могу реализовать эту библиотеку на .pid & pcntl, но я не хочу этого делать, мне это не надо, это не даёт ничего кроме как дополнительного ограничение на использование её только под теми серверами, под которыми у меня есть root-доступ.

                                          Вам хочется на .pid & pcntl всё переписать — перепишите :)
                                          А вас потом попросят на VPS-хостинге что-нибудь аналогичное сделать и вы поднимете этот топик и скачаете scriptAlone))))
                                            0
                                            Ого, и за минус в карму отдельное спасибо… Ну что ж… от добра добра не ищут…
                                              0
                                              Читайте <a href=«habrahabr.ru/blogs/php/83506/#comment_2491031'>этот коммент и не дурите голову.

                                              Возможно не я один тут с вами не согласен.

                                              Меня вообще честно говоря больше интересует статистика скачиваний библиотеки, чем ваши комментарии. Для меня эта статистика более показательный фактор того, что библиотека вполне адекватна.
                                                0
                                                «библиотеку» вашу смотрят, и врядли хоть кто-нибудь будет ее использовать.--
                          0
                          По моему вы не совсем понимаете тему о которой пишете, вам люди объясняют что вы изобрели пятиколёсный велосипед, а вы упираетесь. Почитайте www.php.net/manual/en/refs.fileprocess.process.php, тут ведь сами разработчик, за вас, позаботились об этом…
                            0
                            Вы комментарии читали? Я уже много раз ответил на все такие вопросы.

                            Всё я понимаю. Вы мне что предлагаете экстеншены использовать, системные вызовы на уровне ОС? А представьте, что у меня нет такой возможности. Что у меня VPS хостинг и прав нет на такие выкрунтасы.

                            Вы напишите альтренативный вариант на pcntl и выложите, посмотрим какой процент людей его использовать возьмётся, когда у них прав доступа не хватит, даже чтобы example вашей библиотеки запустить.
                              0
                              Ммм… я посмотрел в википедии определение VPS и выяснил:

                              Каждый VPS имеет собственную копию системы, с правами доступа уровня root (UID: 0) для Unix или Administrator для Windows, что позволяет производить компиляцию, установку собственного программного обеспечения с изменённой конфигурацией.
                                –1
                                И правда)))

                                Я то думал, что VPS это сокращённое название virtual-shared хостинга! Ну, теперь увы комменты не подправить… так что просто учитывайте где я писал про VPS имелось в виду shared virtual хостинг.

                                Спасибо за поправку!
                            0
                            Я не программист, поэтому когда мне надо было решить аналогичную задачу, при помощи функции touch() создавал сигнальный файл, наличие которого служило индикатором того, что процесс был запущен.

                            Кроме того, у файла появлялись дата и время создания. И можно было видеть, что если файл существует дольше, чем таймлимит, то скрипт вылетел (при правильной работе в конце скрипта сигнальный файл удалялся).
                              0
                              А, да, забыл, я потом модифицировал этот способ для хостингов, на которых почему-то не удавалось создать файл (права какие-то и т.п.) — в базу данных записывал временную метку запуска скрипта.
                                0
                                Но у вас я так понял не учитывались следующие моменты:

                                Если скрипт подвисает на выполнении одной из итераций, то он должен быть перезапущен как можно скорее.

                                Скрипт должен перезапускаться каждые 5-10 часов, чтобы предотвратить возможные утечки памяти. Остановка скрипта в этом случае должна производиться безопасным образом (когда задачи текущей итерации выполнены).

                                Необходимо наличие возможности принудительно остановить выполнение скрипта.
                                  0
                                  Да, у меня не был учтен перезапуск скрипта, там по таймлимиту все завершалось.

                                  Перезапуск каждые 5-10 часов сделать несложно — при каждой итерации проверять, сколько натикало, да завершать скрипт. Но, поскольку они и так по таймлимиту завершались, то такой необходимости тоже не было.

                                  Принудительно остановить — да, не делал такого, тоже не было необходимости.

                                  Из требований к моему скрипту: работа на шаред-хостингах, где не всегда есть права на запись (разные хостинги, разные юзеры), но есть мускуль (вордпрессовский плагин писал), а еще есть таймлимит.

                                  Это версия плагина Parasie Eliminator, которую я сейчас переписываю. Та надо проверять базу каментов по базе спамерских урлов и сигнатур. Базы того и другого могут быть большими весьма, а хостинги — шареды. Вот базу надо проверять по кускам, при этом не допустить перерасхода ресурсов шареда.

                                  Для этого и делал свой костыль :)
                                    0
                                    У меня, кстати, в разработке еще решение смешанное было, чтобы параллельно запущенные копии скрипта проверяли разные куски БД.
                                      0
                                      Неплохо!

                                      Но странно, что это за shared-хостинг такой, который прав на запись не даёт. Даже не знал, что такие бывают.

                                      Перезапуск каждые 5-10 часов сделать несложно — при каждой итерации проверять, сколько натикало, да завершать скрипт. Но, поскольку они и так по таймлимиту завершались, то такой необходимости тоже не было.


                                      На самом деле, лучше чтобы скрипт завершался в перерывах между итерациями, а не когда у него time_limit закончится. А то представьте если у вас не транзакционная БД и скрипт остановится в промежутке между выполнением двух запросов (UPDATE / INSERT) одного сценария. Целостность БД может пострадать и сайт перестанет работать.
                                        0
                                        Бывают сочетания шаред-хостингов и владельцев, при которых чудесным образом не получается записать сигнальный файл. Сам бы не подумал, если бы не бета-тестирование. Вариант с файлом работает гораздо быстрее, но БД — у всех (без БД не бывает блогов на «Вордпрессе»).

                                        Чтобы не страдала БД, я скрипт заставил работать небольшими кусочками.

                                        Была мысль получать значение таймлимита, затем определять, сколько уже прошло времени и, исходя из этого, брать размер задачи (проверка идет в то время, которое остается от таймлимита после окончания формирования основной части страницы блога для пользователя).
                                      0
                                      Принудительную остановку можно, кстати, сделать через создание стоп-сигнального файла :)
                                        0
                                        В scriptAlone так и сделано :)
                                          0
                                          Не допер сразу :)

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое