У меня есть PHP script, который занимает много времени (5-30 минут). На всякий случай это важно, script использует завиток для очистки данных с другого сервера. Именно по этой причине он занимает так много времени; он должен дождаться загрузки каждой страницы до ее обработки и перехода к следующему.
Я хочу, чтобы иметь возможность инициировать script, и пусть это будет, пока не будет сделано, что установит флаг в таблице базы данных.
Мне нужно знать, как можно завершить HTTP-запрос до завершения работы script. Кроме того, это лучший способ сделать это 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. В основном это говорит: "Не важно, что делает пользователь, запустите этот script, пока он не будет завершен. Это несколько опасно, если это публичный сайт (потому что возможно, что вы закончите с 20 ++ версиями script, запущенных одновременно, если он инициирован 20 раз).
"Чистый" способ (по крайней мере, IMHO) заключается в установке флага (например, в db), когда вы хотите инициировать процесс и запускать cronjob каждый час (или так), чтобы проверить, установлен ли этот флаг. Если он установлен, запускается длительный script, если он НЕ установлен, ничего не происходит.
header("Connection: close", true);
, И не забудьте промыть ()
Вы можете использовать exec или system, чтобы начать фоновое задание, а затем выполните эту работу.
Кроме того, есть более эффективные подходы к очистке сети, которую вы используете. Вы можете использовать поточный подход (несколько потоков, выполняющих одну страницу за раз), или один с помощью eventloop (один поток делает несколько страниц за раз). Мой личный подход с использованием Perl будет использовать AnyEvent:: HTTP.
ETA: symcbean объяснил, как правильно отделить фоновый процесс здесь.
Нет, PHP не лучшее решение.
Я не уверен в Ruby или Perl, но с Python вы можете переписать свой скребок страницы на многопоточность, и он, вероятно, будет работать как минимум на 20 раз быстрее. Написание многопоточных приложений может быть чем-то сложным, но самое первое приложение Python, которое я написал, было скремблером страницы muttti. И вы можете просто вызвать Python script из вашей PHP-страницы, используя одну из функций выполнения оболочки.
Да, вы можете сделать это на PHP. Но в дополнение к PHP было бы разумно использовать диспетчер очереди. Здесь стратегия:
Разделите свою большую задачу на более мелкие задачи. В вашем случае каждая задача может загружать одну страницу.
Отправьте каждую маленькую задачу в очередь.
Запустите ваши рабочие места в очереди.
Использование этой стратегии имеет следующие преимущества:
Для длительных задач у него есть возможность восстановления в случае возникновения фатальной проблемы в середине прогона - нет необходимости начинать с самого начала.
Если ваши задачи не должны запускаться последовательно, вы можете запускать нескольких рабочих одновременно для запуска задач.
У вас есть множество вариантов (это всего лишь несколько):
PHP может быть или не быть лучшим инструментом, но вы знаете, как его использовать, а остальная часть вашего приложения написана с его использованием. Эти два качества, в сочетании с тем, что PHP "достаточно хороши", делают довольно убедительный аргумент в пользу его использования вместо Perl, Ruby или Python.
Если ваша цель - изучить другой язык, выберите его и используйте. Любой язык, который вы упомянули, будет выполнять эту работу, без проблем. Мне нравится Perl, но то, что вам нравится, может быть другим.
Symcbean имеет несколько хороших советов о том, как управлять фоновыми процессами по его ссылке.
Короче говоря, напишите CLI PHP script для обработки длинных битов. Убедитесь, что он каким-то образом сообщает статус. Сделайте php-страницу для обработки обновлений состояния, используя AJAX или традиционные методы. Ваш запуск script запустит процесс, запущенный в его собственном сеансе, и вернет подтверждение, что процесс идет.
Удачи.
Я хотел бы предложить решение, немного отличающееся от symcbean, главным образом потому, что у меня есть дополнительное требование, чтобы длительный процесс нужно запускать как другой пользователь, а не как пользователь apache/www-data.
Первое решение с использованием cron для опроса таблицы фоновых задач:
Второе решение с использованием средства инициализации Linux:
В моем посте можно найти дополнительную информацию: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html
Я понимаю, что это довольно старый вопрос, но хотел бы дать ему шанс. Этот script пытается обратиться к начальному стартовому звонку, чтобы закончить быстро и отрубить большую нагрузку на более мелкие куски. Я не тестировал это решение.
<?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.';
}
Вы можете отправить его как запрос XHR (Ajax). Клиенты обычно не имеют тайм-аута для XHR, в отличие от обычных HTTP-запросов.
Я согласен с ответами, которые говорят, что это должно выполняться в фоновом режиме. Но также важно, чтобы вы сообщали о статусе, чтобы пользователь знал, что работа выполняется.
При получении запроса PHP для запуска процесса вы можете сохранить в базе данных представление задачи с уникальным идентификатором. Затем запустите процесс очистки экрана, передав ему уникальный идентификатор. Сообщайте iPhone-приложение, что задача была запущена, и что он должен проверить указанный URL-адрес, содержащий новый идентификатор задачи, чтобы получить последний статус. Приложение iPhone теперь может опросить (или даже "длинный опрос" ) этот URL. Тем временем фоновый процесс будет обновлять представление базы данных задачи, поскольку он работал с процентом завершения, текущим шагом или любыми другими индикаторами состояния. И когда он закончится, он установит завершенный флаг.
Не лучший подход, как многие здесь заявили, но это может помочь:
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
если у вас длинный script, затем разделите работу страницы с помощью параметра ввода для каждой задачи. (тогда каждая страница действует как поток) если страница имеет 1 lac product_keywords длинный цикл процесса, то вместо цикла сделать логику для одного ключевого слова и передать это ключевое слово из магии или 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/questions/5905877/how-to-run-the-php-code-asynchronous
то, что я ВСЕГДА использую, является одним из этих вариантов (поскольку разные варианты 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 перенаправил вывод.
Используйте прокси для делегирования запроса.
Я сделал аналогичные вещи с Perl, double fork() и отсоединением от родительского процесса. Вся работа по настройке HTTP должна выполняться в разветвленном процессе.
Goutte
иGuzzle
для реализации потоков параллелизма. Вы также можете взглянуть наGearman
для запуска параллельных запросов в виде рабочих.