Лучший способ управления долговременным скриптом php?

У меня есть PHP-скрипт, который занимает много времени (5-30 минут). На всякий случай это важно, скрипт использует завиток для очистки данных с другого сервера. Это причина, по которой это происходит так долго; он должен дождаться загрузки каждой страницы до ее обработки и перехода к следующему.

Я хочу, чтобы иметь возможность инициировать скрипт и пусть это будет, пока это не будет сделано, что установит флаг в таблице базы данных.

Мне нужно знать, как можно завершить HTTP-запрос до того, как скрипт будет запущен. Кроме того, является ли php-скрипт лучшим способом для этого?

Related of "Лучший способ управления долговременным скриптом php?"

Конечно, это можно сделать с помощью PHP, однако вы не должны делать это в качестве фоновой задачи – новый процесс должен быть удален из группы процессов, в которой он запущен.

Поскольку люди продолжают давать тот же неправильный ответ на этот FAQ, я написал более полный ответ здесь:

http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html

Из комментариев:

Краткая версия – shell_exec('echo /usr/bin/php -q longThing.php | at now'); но причины, почему это немного длиннее для включения здесь.

Быстрый и грязный способ заключается в использовании функции ignore_user_abort в php. В основном это говорит: «Не важно, что делает пользователь, запустите этот скрипт до его завершения. Это несколько опасно, если это публичный сайт (потому что возможно, что в итоге вы получите 20 ++ версий скрипта, запущенного одновременно, если оно инициировано 20 раз).

«Чистый» способ (по крайней мере, IMHO) заключается в установке флага (например, в db), когда вы хотите инициировать процесс и запускать cronjob каждый час (или так), чтобы проверить, установлен ли этот флаг. Если он установлен, запускается длительный скрипт, если он НЕ установлен, ничего не происходит.

Вы можете использовать exec или system, чтобы запустить фоновое задание, а затем выполнить эту работу.

Кроме того, есть более эффективные подходы к очистке сети, которую вы используете. Вы можете использовать поточный подход (несколько потоков, выполняющих одну страницу за раз), или один с помощью eventloop (один поток делает несколько страниц за раз). Мой личный подход с использованием Perl будет использовать AnyEvent :: HTTP .

ETA: symcbean объяснил, как правильно отделить фоновый процесс.

Нет, PHP не лучшее решение.

Я не уверен в Ruby или Perl, но с Python вы можете переписать свой скребок страницы на многопоточность, и он, вероятно, будет работать как минимум на 20 раз быстрее. Написание многопоточных приложений может быть чем-то сложным, но самое первое приложение Python, которое я написал, было скремблером страницы muttti. И вы можете просто вызвать Python-скрипт из вашей PHP-страницы, используя одну из функций выполнения оболочки.

PHP может быть или не быть лучшим инструментом, но вы знаете, как его использовать, а остальная часть вашего приложения написана с его использованием. Эти два качества, в сочетании с тем, что PHP «достаточно хороши», делают довольно убедительный аргумент в пользу его использования вместо Perl, Ruby или Python.

Если ваша цель – изучить другой язык, выберите его и используйте. Любой язык, который вы упомянули, будет выполнять эту работу, без проблем. Мне нравится Perl, но то, что вам нравится, может быть другим.

У Symcbean есть несколько хороших советов о том, как управлять фоновыми процессами по его ссылке.

Короче говоря, напишите PHP-скрипт CLI для обработки длинных битов. Убедитесь, что он каким-то образом сообщает статус. Сделайте php-страницу для обработки обновлений состояния, используя AJAX или традиционные методы. Ваш сценарий запуска начнет процесс, запущенный в его собственной сессии, и вернет подтверждение, что процесс идет.

Удачи.

Вы можете отправить его как запрос XHR (Ajax). Клиенты обычно не имеют тайм-аута для XHR, в отличие от обычных HTTP-запросов.

Да, вы можете сделать это на PHP. Но в дополнение к PHP было бы разумно использовать диспетчер очереди. Вот стратегия:

  1. Разбейте свою большую задачу на более мелкие задачи. В вашем случае каждая задача может загружать одну страницу.

  2. Отправляйте каждую маленькую задачу в очередь.

  3. Где-то запустите своих рабочих мест.

Использование этой стратегии имеет следующие преимущества:

  1. Для длительных задач он имеет возможность восстановления в случае возникновения фатальной проблемы в середине прогона – нет необходимости начинать с самого начала.

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

У вас есть множество вариантов (это всего лишь несколько):

  1. RabbitMQ ( https://www.rabbitmq.com/tutorials/tutorial-one-php.html )
  2. ZeroMQ ( http://zeromq.org/bindings:php )
  3. Если вы используете фреймворк Laravel, очереди построены ( https://laravel.com/docs/5.4/queues ), с драйверами для AWS SES, Redis, Beanstalkd

Я согласен с ответами, которые говорят, что это должно выполняться в фоновом процессе. Но также важно, чтобы вы сообщали о статусе, чтобы пользователь знал, что работа выполняется.

Получив PHP-запрос для запуска процесса, вы можете сохранить в базе данных представление задачи с уникальным идентификатором. Затем запустите процесс очистки экрана, передав ему уникальный идентификатор. Сообщайте iPhone-приложение, что задача была запущена, и что он должен проверить указанный URL-адрес, содержащий новый идентификатор задачи, чтобы получить последний статус. Приложение iPhone теперь может опросить (или даже «длинный опрос») этот URL. Тем временем фоновый процесс будет обновлять представление базы данных задачи, поскольку он работал с процентом завершения, текущим шагом или любыми другими индикаторами состояния. И когда он закончится, он установит завершенный флаг.

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

 <?php /** * crawler.php located at http://mysite.com/crawler.php */ // Make sure this script will keep on runing after we close the connection with // it. ignore_user_abort(TRUE); function get_remote_sources_to_crawl() { // Do a database or a log file query here. $query_result = array ( 1 => 'http://exemple.com', 2 => 'http://exemple1.com', 3 => 'http://exemple2.com', 4 => 'http://exemple3.com', // ... and so on. ); // Returns the first one on the list. foreach ($query_result as $id => $url) { return $url; } return FALSE; } function update_remote_sources_to_crawl($id) { // Update my database or log file list so the $id record wont show up // on my next call to get_remote_sources_to_crawl() } $crawling_source = get_remote_sources_to_crawl(); if ($crawling_source) { // Run your scraping code on $crawling_source here. if ($your_scraping_has_finished) { // Update you database or log file. update_remote_sources_to_crawl($id); $ctx = stream_context_create(array( 'http' => array( // I am not quite sure but I reckon the timeout set here actually // starts rolling after the connection to the remote server is made // limiting only how long the downloading of the remote content should take. // So as we are only interested to trigger this script again, 5 seconds // should be plenty of time. 'timeout' => 5, ) )); // Open a new connection to this script and close it after 5 seconds in. file_get_contents('http://' . $_SERVER['HTTP_HOST'] . '/crawler.php', FALSE, $ctx); print 'The cronjob kick off has been initiated.'; } } else { print 'Yay! The whole thing is done.'; } 

Я хотел бы предложить решение, немного отличающееся от symcbean, главным образом потому, что у меня есть дополнительные требования, чтобы длительный процесс нужно запускать как другой пользователь, а не как пользователь apache / www-data.

Первое решение, использующее cron для опроса таблицы фоновых задач:

  • Веб-страница PHP вставляет в таблицу фоновых задач, указывается «SUBMITTED»
  • cron запускается один раз каждые 3 минуты, используя другого пользователя, запуская скрипт PHP CLI, который проверяет таблицу фоновых задач для строк «SUBMITTED»
  • PHP CLI обновит столбец состояния в строке в «ОБРАБОТКА» и начнет обработку, после завершения он будет обновлен до «COMPLETED»

Второе решение с использованием Linux inotify:

  • Веб-страница PHP обновляет управляющий файл с параметрами, установленными пользователем, а также дает идентификатор задачи
  • shell-скрипт (как пользователь не-www), запускающий inotifywait, будет ждать записи файла управления
  • после того, как файл управления будет записан, событие close_write будет поднято, скрипт оболочки будет продолжен
  • сценарий оболочки выполняет PHP CLI для выполнения долгого процесса
  • PHP CLI записывает вывод в файл журнала, идентифицированный идентификатором задачи, или, как альтернативно, обновляет ход в таблице состояния
  • Веб-страница PHP могла опросить файл журнала (на основе идентификатора задачи), чтобы показать ход долгого процесса или он может также запрашивать таблицу состояния

Некоторая дополнительная информация может быть найдена в моем сообщении: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html

Я сделал аналогичные вещи с Perl, double fork () и отсоединением от родительского процесса. Вся работа по настройке HTTP должна выполняться в разветвленном процессе.

Используйте прокси для делегирования запроса.

то, что я ВСЕГДА использую, является одним из этих вариантов (поскольку разные варианты Linux имеют разные правила обработки выходных данных / некоторые программы выводятся по-разному):

Вариант I @exec ('./ myscript.php \ 1> / dev / null \ 2> / dev / null &');

Вариант II @exec ('php -f myscript.php \ 1> / dev / null \ 2> / dev / null &');

Вариант III @exec ('nohup myscript.php \ 1> / dev / null \ 2> / dev / null &');

Вы можете установить установку «nohup». Но, например, когда я автоматизировал конвертации видео FFMPEG, интерфейс вывода каким-то образом не обрабатывался на 100% путем перенаправления выходных потоков 1 и 2, поэтому я использовал nohup AND перенаправил вывод.

если у вас длинный скрипт, разделите работу страницы с помощью параметра ввода для каждой задачи. (тогда каждая страница действует как поток), то есть если страница имеет 1 lac product_keywords длинный цикл процесса, то вместо цикла сделать логику для одного ключевого слова и передать это ключевое слово от магии или cornjobpage.php (в следующем примере)

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

cornjobpage.php // главная страница

  <?php post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue"); //post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2"); //post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue"); //call as many as pages you like all pages will run at once independently without waiting for each page response as asynchronous. ?> <?php /* * Executes a PHP page asynchronously so the current page does not have to wait for it to finish running. * */ function post_async($url,$params) { $post_string = $params; $parts=parse_url($url); $fp = fsockopen($parts['host'], isset($parts['port'])?$parts['port']:80, $errno, $errstr, 30); $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like $out.= "Host: ".$parts['host']."\r\n"; $out.= "Content-Type: application/x-www-form-urlencoded\r\n"; $out.= "Content-Length: ".strlen($post_string)."\r\n"; $out.= "Connection: Close\r\n\r\n"; fwrite($fp, $out); fclose($fp); } ?> 

testpage.php

  <? echo $_REQUEST["Keywordname"];//case1 Output > testValue ?> 

PS: если вы хотите отправить параметры URL-адреса в качестве цикла, выполните следующий ответ: https://stackoverflow.com/a/41225209/6295712