Лучший способ управлять долгосрочным PHP-скриптом? - PullRequest
72 голосов
/ 06 февраля 2010

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

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

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

Ответы [ 15 ]

102 голосов
/ 07 февраля 2010

Конечно, это можно сделать с помощью 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');, но причины, по которым здесь много места для включения,

11 голосов
/ 06 февраля 2010

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

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

8 голосов
/ 06 февраля 2010

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

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

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

5 голосов
/ 23 мая 2017

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

  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
5 голосов
/ 06 февраля 2010

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

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

3 голосов
/ 08 февраля 2010

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

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

Symcbean дает несколько полезных советов о том, как управлять фоновыми процессами в своей ссылке.

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

Удачи.

1 голос
/ 31 января 2016

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

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

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

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

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

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

1 голос
/ 27 июня 2013

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

<?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.';
}
1 голос
/ 07 февраля 2010

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

1 голос
/ 06 февраля 2010

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

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

...