오래 실행되는 PHP 스크립트를 관리하는 가장 좋은 방법은 무엇입니까?
완료하는 데 오랜 시간 (5-30 분)이 걸리는 PHP 스크립트가 있습니다. 중요한 경우를 대비하여 스크립트는 curl을 사용하여 다른 서버에서 데이터를 스크랩합니다. 이것이 너무 오래 걸리는 이유입니다. 처리하고 다음 페이지로 이동하기 전에 각 페이지가로드 될 때까지 기다려야합니다.
스크립트를 시작하고 완료 될 때까지두면 데이터베이스 테이블에 플래그가 설정됩니다.
내가 알아야 할 것은 스크립트 실행이 완료되기 전에 http 요청을 종료 할 수있는 방법입니다. 또한 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)은 프로세스를 시작하고 매시간 (또는 그 이상) cronjob을 실행하여 해당 플래그가 설정되었는지 확인하려는 경우 플래그를 설정하는 것입니다 (예 : db). 설정되어 있으면 장기 실행 스크립트가 시작되고 설정되지 않으면 아무 일도 발생하지 않습니다.
exec 또는 system 을 사용 하여 백그라운드 작업을 시작한 다음 그 작업을 수행 할 수 있습니다.
또한 사용중인 웹을 스크래핑하는 더 나은 방법이 있습니다. 스레드 방식 (한 번에 한 페이지를 수행하는 여러 스레드)을 사용하거나 이벤트 루프를 사용하는 방법 (한 번에 여러 페이지를 수행하는 스레드 하나)을 사용할 수 있습니다. Perl을 사용하는 개인적인 접근 방식은 AnyEvent :: HTTP를 사용하는 것 입니다.
ETA : symcbean 은 여기서 백그라운드 프로세스를 올바르게 분리하는 방법을 설명 했습니다 .
아니요, PHP는 최상의 솔루션이 아닙니다.
Ruby 나 Perl에 대해서는 잘 모르겠지만 Python을 사용하면 페이지 스크레이퍼를 다중 스레드로 다시 작성할 수 있으며 아마도 최소 20 배 더 빠르게 실행될 것입니다. 다중 스레드 앱을 작성하는 것은 다소 어려울 수 있지만 제가 작성한 첫 번째 Python 앱은 다중 스레드 페이지 스크레이퍼였습니다. 그리고 쉘 실행 함수 중 하나를 사용하여 PHP 페이지 내에서 Python 스크립트를 호출 할 수 있습니다.
네, PHP로 할 수 있습니다. 그러나 PHP 외에도 큐 관리자를 사용하는 것이 좋습니다. 전략은 다음과 같습니다.
큰 작업을 작은 작업으로 나눕니다. 귀하의 경우 각 작업은 단일 페이지를로드 할 수 있습니다.
각 작은 작업을 대기열로 보냅니다.
대기열 작업자를 어딘가에서 실행하십시오.
이 전략을 사용하면 다음과 같은 이점이 있습니다.
장기간 실행되는 작업의 경우 실행 도중에 치명적인 문제가 발생하는 경우 복구 할 수있는 기능이 있으며 처음부터 시작할 필요가 없습니다.
작업을 순차적으로 실행할 필요가없는 경우 여러 작업자를 실행하여 동시에 작업을 실행할 수 있습니다.
다양한 옵션이 있습니다 (몇 가지에 불과 함).
- RabbitMQ ( https://www.rabbitmq.com/tutorials/tutorial-one-php.html )
- ZeroMQ ( http://zeromq.org/bindings:php )
- Laravel 프레임 워크를 사용하는 경우 AWS SES, Redis, Beanstalkd 용 드라이버와 함께 대기열이 기본 제공됩니다 ( https://laravel.com/docs/5.4/queues )
PHP가 최고의 도구 일 수도 있고 아닐 수도 있지만 사용 방법을 알고 있으며 나머지 애플리케이션은이를 사용하여 작성됩니다. PHP가 "충분히 좋다"는 사실과 결합 된이 두 가지 특성은 Perl, Ruby 또는 Python 대신 PHP를 사용하는 데있어 매우 강력한 사례를 만듭니다.
목표가 다른 언어를 배우는 것이라면 하나를 선택하여 사용하십시오. 언급 한 모든 언어가 문제없이 작동합니다. 나는 Perl을 좋아하지만 당신이 좋아하는 것은 다를 수 있습니다.
Symcbean은 그의 링크에서 백그라운드 프로세스를 관리하는 방법에 대한 좋은 조언을 제공합니다.
간단히 말해, 긴 비트를 처리하는 CLI PHP 스크립트를 작성하십시오. 어떤 방식 으로든 상태를보고하는지 확인하십시오. AJAX 또는 기존 방법을 사용하여 상태 업데이트를 처리하는 PHP 페이지를 만듭니다. 킥오프 스크립트는 자체 세션에서 실행되는 프로세스를 시작하고 프로세스가 진행되고 있다는 확인을 반환합니다.
행운을 빕니다.
나는 이것이 백그라운드 프로세스에서 실행되어야한다는 답변에 동의합니다. 그러나 사용자가 작업이 완료되었음을 알 수 있도록 상태를보고하는 것도 중요합니다.
프로세스 시작을위한 PHP 요청을 받으면 고유 식별자가있는 작업 표현을 데이터베이스에 저장할 수 있습니다. 그런 다음 화면 스크래핑 프로세스를 시작하여 고유 식별자를 전달합니다. 작업이 시작되었으며 최신 상태를 얻기 위해 새 작업 ID가 포함 된 지정된 URL을 확인해야한다고 iPhone 앱에 다시보고하십시오. 이제 iPhone 애플리케이션이이 URL을 폴링 (또는 "긴 폴") 할 수 있습니다. 그 동안 백그라운드 프로세스는 완료율, 현재 단계 또는 원하는 기타 상태 표시기로 작업 한 작업의 데이터베이스 표현을 업데이트합니다. 완료되면 완료된 플래그를 설정합니다.
XHR (Ajax) 요청으로 보낼 수 있습니다. 클라이언트는 일반적인 HTTP 요청과 달리 일반적으로 XHR에 대한 시간 제한이 없습니다.
나는 이것이 꽤 오래된 질문이라는 것을 알고 있지만 한번 시도해보고 싶습니다. 이 스크립트는 초기 킥오프 호출을 처리하여 빠르게 완료하고 무거운로드를 더 작은 청크로 분할하려고합니다. 이 솔루션을 테스트하지 않았습니다.
<?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.';
}
장기 실행 프로세스를 apache / www-data 사용자가 아닌 다른 사용자로 실행해야한다는 추가 요구 사항이 있기 때문에 symcbean과 약간 다른 솔루션을 제안하고 싶습니다.
cron을 사용하여 백그라운드 작업 테이블을 폴링하는 첫 번째 솔루션 :
- PHP 웹 페이지가 백그라운드 작업 테이블에 삽입되고 'SUBMITTED'상태
- cron은 다른 사용자를 사용하여 3 분마다 한 번씩 실행되며 'SUBMITTED'행에 대한 백그라운드 작업 테이블을 확인하는 PHP CLI 스크립트를 실행합니다.
- PHP CLI will update the state column in the row into 'PROCESSING' and begin processing, after completion it will be updated to 'COMPLETED'
Second solution using Linux inotify facility:
- PHP web page updates a control file with the parameters set by user, and also giving a task id
- shell script (as a non-www user) running inotifywait will wait for the control file to be written
- after control file is written, a close_write event will be raised an the shell script will continue
- shell script executes PHP CLI to do the long running process
- PHP CLI writes the output to a log file identified by task id, or alternatively updates progress in a status table
- PHP web page could poll the log file (based on task id) to show progress of the long running process, or it could also query status table
Some additional info could be found in my post : http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html
I have done similar things with Perl, double fork() and detaching from parent process. All http fetching work should be done in forked process.
Use a proxy to delegate the request.
what I ALWAYS use is one of these variants (because different flavors of Linux have different rules about handling output/some programs output differently):
Variant I @exec('./myscript.php \1>/dev/null \2>/dev/null &');
Variant II @exec('php -f myscript.php \1>/dev/null \2>/dev/null &');
Variant III @exec('nohup myscript.php \1>/dev/null \2>/dev/null &');
You might havet install "nohup". But for example, when I was automating FFMPEG video converstions, the output interface somehow wasn't 100% handled by redirecting output streams 1 & 2, so I used nohup AND redirected the output.
if you have long script then divide page work with the help of input parameter for each task.(then each page act like thread) i.e if page has 1 lac product_keywords long process loop then instead of loop make logic for one keyword and pass this keyword from magic or cornjobpage.php(in following example)
and for background worker i think you should try this technique it will help to call as many as pages you like all pages will run at once independently without waiting for each page response as asynchronous.
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:if you want to send url parameters as loop then follow this answer :https://stackoverflow.com/a/41225209/6295712
Not the best approach, as many stated here, but this might help:
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
If the desired output of your script is some processing, not a webpage, then I believe the desired solution is to run your script from shell, simply as
php my_script.php
참고URL : https://stackoverflow.com/questions/2212635/best-way-to-manage-long-running-php-script
'IT박스' 카테고리의 다른 글
Swift에서 @autoreleasepool과 동등한 것은 무엇입니까? (0) | 2020.10.19 |
---|---|
Windows에서 Python의 로케일을 설정하는 올바른 방법은 무엇입니까? (0) | 2020.10.19 |
Web.Config 디버그 / 릴리스 (0) | 2020.10.19 |
gson.toJson ()에서 StackOverflowError가 발생합니다. (0) | 2020.10.19 |
0과 비교할 때 int 연산자! = 및 == (0) | 2020.10.19 |