it-swarm.com.ru

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

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

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

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

68
kbanman

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

100
symcbean

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

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

11
FlorianH

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

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

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

8
Leon Timmermans

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

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

5
jamieb

Да, вы можете сделать это на 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
4
aljo f

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

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

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

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

Удачи.

3
daotoad

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

1
JAL

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

<?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
Francisco Luz

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

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

1
Jacob

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

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

  • Веб-страница PHP вставляется в таблицу фоновых задач, состояние «ОТПРАВЛЕНО»
  • 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
YudhiWidyatama

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

0
Alexandr Ciornii

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

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

cornjobpage.php // mainpage

    <?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

0
Hassan Saeed

вСЕГДА я использую один из этих вариантов (потому что разные версии 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, чтобы перенаправить вывод.

0
dr burns

Не лучший подход, как многие здесь заявили, но это может помочь:

ignore_user_abort(1); // run script in background even if user closes browser
set_time_limit(1800); // run it for 30 minutes

// Long running script here
0
Lucas Bustamante

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

0
zerodin