Фоновый процесс PHP в BSD использует 100% ЦП

У меня есть PHP-скрипт, который работает как фоновый процесс. Этот скрипт просто использует fopen для чтения из Twitter Streaming API. По сути, http-соединение, которое никогда не заканчивается. К сожалению, я не могу опубликовать сценарий, потому что он проприетарный. Скрипт в Ubuntu работает нормально и использует очень мало ресурсов процессора. Однако в BSD сценарий всегда использует почти 100% ЦП. Сценарий отлично работает на обеих машинах и является одним и тем же сценарием. Может ли кто-нибудь подумать о чем-то, что могло бы указать мне в правильном направлении, чтобы исправить это? Это первый PHP-скрипт, который я написал для последовательной работы в фоновом режиме.

Скрипт представляет собой бесконечный цикл, он считывает данные и записывает их в json-файл каждую минуту. Сценарий будет записывать в базу данных MySQL всякий раз, когда происходит переподключение, что обычно происходит после нескольких дней работы. Сценарий больше ничего не делает и не очень длинный. У меня мало опыта работы с BSD или написания PHP-скриптов, запускающих бесконечные циклы. Заранее спасибо за любые предложения, дайте мне знать, если это относится к другому StackExchange. Я постараюсь ответить на любые вопросы как можно быстрее, потому что я понимаю, что вопрос очень расплывчатый.


person Caimen    schedule 13.10.2011    source источник
comment
Профиль с xdebug и kcachegrind? Трассировка с помощью gdb? Можете ли вы создать образец сценария, воспроизводящий то же поведение, которым вы можете поделиться с нами?   -  person Frank Farmer    schedule 14.10.2011
comment
Я никогда раньше не использовал xdebug на сервере разработки, только локально. Я посмотрю на это. Тем временем я попытаюсь создать короткий пример сценария, который может воспроизвести это поведение.   -  person Caimen    schedule 14.10.2011
comment
Вы можете посмотреть на что-то вроде этого, чтобы абстрагировать часть кода управления процессами: github.com/shaneharter/PHP -Демон   -  person aknosis    schedule 14.10.2011
comment
Это выглядит интересно. На самом деле я использую Screen как метод запуска фонового процесса вместе с выполнением команд оболочки через PHP. Это действительно хакерство. Возможно, это больше связано с экраном, чем с самим сценарием. Я посмотрю на этот PHP-Daemon, он звучит намного лучше, чем screen.   -  person Caimen    schedule 14.10.2011
comment
Ответ DaveRandom привел меня к выводу, что по какой-то странной причине экран является преступником, а не реальным сценарием. Скрипт нормально запускается из командной строки. Однако запуск сценария за экраном выходит из-под контроля. Экран не выходит из-под контроля в Ubuntu, только в BSD. Я собираюсь изучить PHP-Daemon и другие решения для этого. Спасибо за помощь и особенно за решение PHP-Daemon Aknosis.   -  person Caimen    schedule 14.10.2011


Ответы (3)


Не видя сценария, очень сложно дать вам окончательный ответ, однако вам нужно убедиться, что ваш сценарий ожидает данные надлежащим образом. Чего вам абсолютно точно нельзя делать, так это вызывать stream_set_timeout($fp, 0); или stream_set_blocking($fp, 0); для указателя файла.

Базовая структура скрипта, позволяющего избежать гонок, будет примерно такой:

// Open the file pointer and set blocking mode
$fp = fopen('http://www.domain.tld/somepage.file','r');
stream_set_timeout($fp, 1);
stream_set_blocking($fp, 1);

while (!feof($fp)) { // This should loop until the server closes the connection

  // This line should be pretty much the first line in the loop
  // It will try and fetch a line from $fp, and block for 1 second
  // or until one is available. This should help avoid racing
  // You can also use fread() in the same way if necessary
  if (($str = fgets($fp)) === FALSE) continue;

  // rest of app logic goes here

}

Вы также можете использовать sleep()/usleep(), чтобы избежать гонок, но лучший подход — полагаться на вызов блокирующей функции для блокировки. Если это работает в одной ОС, но не работает в другой, попробуйте явно установить режимы/поведение блокировки, как указано выше.

Если вы не можете заставить это работать с вызовом fopen(), передающим URL-адрес HTTP, это может быть проблемой с реализацией оболочки HTTP в PHP. Чтобы обойти это, вы можете использовать fsockopen() и обработать запрос самостоятельно. Это не так уж сложно, особенно если вам нужно отправить только один запрос и прочитать ответ постоянного потока.

person DaveRandom    schedule 13.10.2011
comment
Я только что попробовал ваш код. Код работает нормально, но я все еще работаю на 100%. Я помню, что у меня были какие-то проблемы с fsockopen(), но я посмотрю на это. - person Caimen; 14.10.2011
comment
Если процесс запущен на 100%, это предполагает, что вызов fgets() не блокируется. Если вы добавите строку echo time(); непосредственно перед этой строкой, я ожидаю увидеть временную метку UNIX один раз в секунду (когда нет данных для получения из сокета) - можете ли вы проверить это и посмотреть, так ли это? - person DaveRandom; 14.10.2011
comment
Вы правы, это отлично работает, когда я запускаю команду php. Однако проблема не в команде php, а в screen. Я использовал экран для запуска процесса как своего рода демона. Я знаю, что это очень хакерски, но ваш ответ заставил меня понять это. Спасибо за помощь и показ мне, как это должно быть сделано. Я собираюсь попробовать PHP-Daemon, как предложил другой пользователь. Предоставленный вами код отлично работает в BSD, если только он не запускается за экраном. По какой-то причине экран выходит из-под контроля с этим конкретным скриптом в BSD, но не в Ubuntu. - person Caimen; 14.10.2011
comment
Если мне нужно запустить PHP-скрипт в качестве демона, я обычно добавляю строку ::respawn:php /path/to/my/script в inittab. Конечно, вы не можете сделать это в BSD, так как у него нет inittab, но должен быть эквивалент, который вы могли бы использовать - я знаю крохи о BSD, поэтому я не могу точно сказать, что это может быть, но что-то должно быть. ... Может быть, поможет это? - person DaveRandom; 14.10.2011
comment
Я проверю это. Спасибо за помощь. - person Caimen; 14.10.2011

Мне кажется, что одна из ваших функций ненадолго блокируется в Linux, но не в BSD. Не видя вашего скрипта, трудно сказать что-то конкретное, но я бы посоветовал добавить usleep() перед следующей итерацией цикла:

usleep(100000); //Sleep for 100ms

Вам не нужен долгий сон... достаточно, чтобы вы не использовали 100% ЦП.

Редактировать: Поскольку вы упомянули, что сейчас у вас нет хорошего способа запустить это в фоновом режиме, я предлагаю проверить это руководство по "демонизации" вашего скрипта. Включен некоторый удобный код для этого. Он даже может создать для вас файл в init.d.

person Brad    schedule 13.10.2011
comment
Я должен был упомянуть, что пробовал с usleep и без него, но безрезультатно. Я думаю, мне нужно найти хорошую статью, объясняющую блокировку. Очень сложно найти информацию, относящуюся к запуску PHP в качестве фонового процесса. Мне нужно найти что-то, чтобы обучить меня этому предмету. - person Caimen; 14.10.2011
comment
@Caimen, запуск имеет фоновый или передний план, не имеет значения. Если вы запускаете PHP отдельно, это почти идентично запуску CGI. Единственный раз, когда вы сталкиваетесь с большими различиями, это если вы запускаете PHP с FastCGI или ISAPI, где PHP размещается в другом процессе. В противном случае процесс есть процесс. - person Brad; 14.10.2011

Как выглядит код, выполняющий фактическое чтение? Вы просто забиваете розетку, пока не получите что-то?

Одним из действительно эффективных способов справиться с этим является использование расширения libevent, но это не для слабаков. единомышленников.

person Evert    schedule 13.10.2011