오래 실행되는 PHP 스크립트를 관리하는 가장 좋은 방법은 무엇입니까?


80

완료하는 데 오랜 시간 (5-30 분)이 걸리는 PHP 스크립트가 있습니다. 중요한 경우를 대비하여 스크립트는 curl을 사용하여 다른 서버에서 데이터를 스크랩합니다. 이것이 너무 오래 걸리는 이유입니다. 처리하고 다음 페이지로 이동하기 전에 각 페이지가로드 될 때까지 기다려야합니다.

스크립트를 시작하고 완료 될 때까지 기다리면 데이터베이스 테이블에 플래그가 설정됩니다.

내가 알아야 할 것은 스크립트 실행이 완료되기 전에 http 요청을 종료 할 수있는 방법입니다. 또한 PHP 스크립트가이를 수행하는 가장 좋은 방법입니까?


1
서버에서 지원하는 언어로 언급하지는 않았지만 Ruby와 Perl을 실행할 수있는 능력이 있다면 아마도 Node.js를 추가 할 수있을 것입니다. : 스크립트는 요청이 완료되기를 기다리는 데 대부분의 시간을 소비합니다. 이는 비동기 패러다임이 뛰어난 영역입니다. 스레드가 없다는 것은 쉬운 동기화를 의미하고 동시성은 빠른 것을 의미합니다.
djfm

PHP로이 작업을 수행 할 수 있습니다. 동시성 스레드를 사용 Goutte하고 Guzzle구현합니다. Gearman작업자의 형태로 병렬 요청을 시작 하도록 조사 할 수도 있습니다 .
Andre Garcia

답변:


114

확실히 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');여기에 포함하기 위해 약간 긴 이유입니다.


이 블로그 게시물이 진정한 답입니다. PHP의 exec 및 시스템에는 잠재적 인 함정이 너무 많습니다.
incredimike 2013 년

2
관련 세부 사항을 답변에 복사 할 가능성이 있습니까? 죽은 블로그로 연결되는 오래된 답변이 너무 많습니다. 그 블로그는 (아직) 죽지 않았지만 언젠가는 될 것입니다.
Murphy

5
짧은 버전은 shell_exec('echo /usr/bin/php -q longThing.php | at now');여기에 포함하기 위해 약간 긴 이유입니다.
symcbean

1
높은 평가를받은 질문에 대해 높은 평가를받은 답변이지만 답변에는 블로그 게시물에 대한 링크가 포함되어 있지 않습니다. meta.stackexchange.com/questions/8231/… 및 / 또는 도움말 센터 에 따라 실제 답변을 추가하십시오
Nanne

1
이 -q 옵션이 무엇을하는지 알 수 있습니까?
Kiren 시바

11

빠르고 더러운 방법은 ignore_user_abortphp 에서 함수 를 사용하는 것입니다. 이것은 기본적으로 다음과 같습니다. 사용자가 무엇을하는지 신경 쓰지 말고 완료 될 때까지이 스크립트를 실행하십시오. 이것은 공개 사이트 인 경우 다소 위험합니다 (20 번 시작하면 동시에 20 ++ 버전의 스크립트가 실행될 수 있기 때문입니다).

"깨끗한"방법 (최소한 IMHO)은 프로세스를 시작하고 매시간 (또는 그 이상) cronjob을 실행하여 플래그가 설정되었는지 확인하려는 경우 플래그를 설정하는 것입니다 (예 : db). 설정되어 있으면 장기 실행 스크립트가 시작되고 설정되지 않으면 아무 일도 발생하지 않습니다.


그래서 "ignore_user_abort"메소드를 사용하면 사용자가 브라우저 창을 닫을 수 있지만 실행이 완료되기 전에 클라이언트에 HTTP 응답을 반환하도록 할 수있는 작업이 있습니까?
kbanman 2010

1
@kbanman 네. 연결을 종료해야합니다 : header("Connection: close", true);. 그리고 () 플러시하는 것을 잊지 마세요
Benubird

8

exec 또는 system 을 사용 하여 백그라운드 작업을 시작한 다음 그 작업을 수행 할 수 있습니다.

또한 사용중인 웹을 스크래핑하는 더 나은 방법이 있습니다. 스레드 방식 (한 번에 한 페이지를 수행하는 여러 스레드)을 사용하거나 이벤트 루프를 사용하는 방법 (한 번에 여러 페이지를 수행하는 스레드 하나)을 사용할 수 있습니다. Perl을 사용하는 개인적인 접근 방식은 AnyEvent :: HTTP를 사용하는 것 입니다.

ETA : symcbean여기서 백그라운드 프로세스를 올바르게 분리하는 방법을 설명 했습니다 .


5
거의 맞습니다. exec 또는 시스템을 사용하는 것만으로도 당신을 괴롭힐 것입니다. 자세한 내용은 내 답장을 참조하십시오.
symcbean 2010

5

아니요, PHP는 최상의 솔루션이 아닙니다.

Ruby 나 Perl에 대해서는 잘 모르겠지만 Python을 사용하면 페이지 스크레이퍼를 다중 스레드로 다시 작성할 수 있으며 아마도 최소 20 배 더 빠르게 실행될 것입니다. 다중 스레드 앱을 작성하는 것은 다소 어려울 수 있지만 제가 작성한 첫 번째 Python 앱은 다중 스레드 페이지 스크레이퍼였습니다. 그리고 쉘 실행 함수 중 하나를 사용하여 PHP 페이지 내에서 Python 스크립트를 호출 할 수 있습니다.


내 스크래핑의 실제 처리 부분은 매우 효율적입니다. 위에서 언급했듯이 나를 죽이는 것은 각 페이지의 로딩입니다. 내가 궁금했던 것은 PHP가 그렇게 오랜 기간 동안 실행될 것인지 여부입니다.
kbanman 2010

나는 파이썬을 배우기 때문에 PHP를 완전히 싫어하기 때문에 약간 편견을 가지고 있습니다. 그러나 두 개 이상의 페이지 (연속)를 스크랩하는 경우 다중 스레드 앱과 병렬로 수행하여 더 나은 성능을 얻을 수 있습니다.
jamieb

1
그런 페이지 스크레이퍼의 예를 보내 주시겠습니까? 아직 파이썬을 다루지 않았기 때문에 많은 것을 볼 수 있도록 도와 줄 것입니다.
kbanman 2010

다시 작성해야한다면 eventlet을 사용합니다. 내 코드를 약 10 배 더 간단하게 만듭니다. eventlet.net/doc
jamieb

5

네, 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 프레임 워크를 사용하는 경우 AWS SES, Redis, Beanstalkd 용 드라이버와 함께 대기열이 내장되어 있습니다 ( https://laravel.com/docs/5.4/queues ).

3

PHP가 최고의 도구 일 수도 있고 아닐 수도 있지만 사용 방법을 알고 있으며 나머지 애플리케이션은이를 사용하여 작성됩니다. 이 두 가지 특성은 PHP가 "충분히 좋다"는 사실과 결합되어 Perl, Ruby 또는 Python 대신 PHP를 사용하는 데있어 매우 강력한 사례입니다.

목표가 다른 언어를 배우는 것이라면 하나를 선택하여 사용하십시오. 언급 한 모든 언어가 문제없이 작동합니다. 나는 Perl을 좋아하지만 당신이 좋아하는 것은 다를 수 있습니다.

Symcbean은 그의 링크에서 백그라운드 프로세스를 관리하는 방법에 대한 좋은 조언을 제공합니다.

간단히 말해, 긴 비트를 처리하는 CLI PHP 스크립트를 작성하십시오. 어떤 방식 으로든 상태를보고하는지 확인하십시오. AJAX 또는 기존 방법을 사용하여 상태 업데이트를 처리 할 PHP 페이지를 만듭니다. 킥오프 스크립트는 자체 세션에서 실행되는 프로세스를 시작하고 프로세스가 진행되고 있다는 확인을 반환합니다.

행운을 빕니다.


1

나는 이것이 백그라운드 프로세스에서 실행되어야한다는 답변에 동의합니다. 그러나 사용자가 작업이 완료되었음을 알 수 있도록 상태를보고하는 것도 중요합니다.

프로세스 시작을위한 PHP 요청을 수신하면 고유 식별자를 사용하여 작업 표현을 데이터베이스에 저장할 수 있습니다. 그런 다음 화면 스크래핑 프로세스를 시작하여 고유 식별자를 전달합니다. 작업이 시작되었으며 최신 상태를 가져 오기 위해 새 작업 ID가 포함 된 지정된 URL을 확인해야한다고 iPhone 앱에 다시보고하십시오. 이제 iPhone 애플리케이션은이 URL을 폴링 (또는 "긴 폴링") 할 수 있습니다. 그 동안 백그라운드 프로세스는 완료율, 현재 단계 또는 원하는 기타 상태 표시기로 작업 한 작업의 데이터베이스 표현을 업데이트합니다. 완료되면 완료된 플래그를 설정합니다.


1

XHR (Ajax) 요청으로 보낼 수 있습니다. 클라이언트는 일반적인 HTTP 요청과 달리 일반적으로 XHR에 대한 시간 제한이 없습니다.


1

나는 이것이 꽤 오래된 질문이라는 것을 알고 있지만 한번 시도해보고 싶습니다. 이 스크립트는 초기 킥오프 호출을 처리하여 신속하게 완료하고 무거운로드를 더 작은 청크로 분할하려고합니다. 이 솔루션을 테스트하지 않았습니다.

<?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 귀하가 제안한 게시물을 읽었으며이 대체 솔루션에 대한 귀하의 생각을 듣고 싶습니다.
Francisco Luz

첫째, 제 첫 봇 (teehee)에 대한 시작 아이디어를 주셨습니다. 둘째, 솔루션의 성능을 어떻게 찾았습니까? 더 많이 작업하고 더 배운 것이 있습니까? 26,000 개의 이미지 (1,3GB)를 통해 준설과 유사한 것을 구현하고 다양한 작업을 수행하는 등의 작업에 관심이 있습니다. 시간이 좀 걸릴 것입니다. 당신은, 사용 간부 인 () 해키 보이지 않는 유일한 솔루션입니다 전율을 또는 Linux (패자는 여전히 윈도우를 사용해야 할 우리의 일부)이 필요합니다. P : 나는 오히려 내 자신보다, 당신의 headbashing에서 배우는 것을 선호합니다
그냥 일반 고

@HighPriestessofTheTech 안녕하세요, 저는 더 이상 가지 않았습니다. 내가 이것을 썼을 때 나는 단지 사고 실험을했을 뿐이었다.
Francisco Luz 2013

1
오 이런 ... 나는 내 자신의 headbashing 학습있을거야 그래서 ... 난 당신이 어떻게되는지 알려 드리겠습니다)
그냥 일반 고

1
나는 이것을 시도했고 꽤 유용하다는 것을 알았습니다.
Alex

1

장기 실행 프로세스를 apache / www-data 사용자가 아닌 다른 사용자로 실행해야한다는 추가 요구 사항이 있기 때문에 symcbean과 약간 다른 솔루션을 제안하고 싶습니다.

cron을 사용하여 백그라운드 작업 테이블을 폴링하는 첫 번째 솔루션 :

  • PHP 웹 페이지가 백그라운드 작업 테이블에 삽입되고 'SUBMITTED'상태
  • cron은 다른 사용자를 사용하여 3 분마다 한 번씩 실행되며 'SUBMITTED'행에 대한 백그라운드 작업 테이블을 확인하는 PHP CLI 스크립트를 실행합니다.
  • PHP CLI는 행의 상태 열을 'PROCESSING'으로 업데이트하고 처리를 시작하며 완료 후 'COMPLETED'로 업데이트됩니다.

Linux inotify 기능을 사용하는 두 번째 솔루션 :

  • PHP 웹 페이지는 사용자가 설정 한 매개 변수로 제어 파일을 업데이트하고 작업 ID도 제공합니다.
  • inotifywait를 실행하는 쉘 스크립트 (www가 아닌 ​​사용자로)는 제어 파일이 작성되기를 기다립니다.
  • 제어 파일이 작성된 후 close_write 이벤트가 발생하고 쉘 스크립트가 계속됩니다.
  • 쉘 스크립트는 PHP CLI를 실행하여 장기 실행 프로세스를 수행합니다.
  • PHP CLI는 작업 ID로 식별되는 로그 파일에 출력을 기록하거나 상태 테이블에서 진행 상황을 업데이트합니다.
  • PHP 웹 페이지는 로그 파일 (작업 ID 기반)을 폴링하여 장기 실행 프로세스의 진행 상황을 표시하거나 상태 테이블을 쿼리 할 수도 있습니다.

내 게시물에서 몇 가지 추가 정보를 찾을 수 있습니다. http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html


0

Perl, double fork () 및 부모 프로세스에서 분리하여 비슷한 작업을 수행했습니다. 모든 http 가져 오기 작업은 분기 된 프로세스에서 수행되어야합니다.



0

내가 항상 사용하는 것은 이러한 변형 중 하나입니다 (리눅스의 다른 버전은 출력 / 일부 프로그램 출력을 다르게 처리하는 규칙이 다르기 때문입니다) :

변형 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 비디오 대화를 자동화 할 때 출력 인터페이스가 출력 스트림 1과 2를 리디렉션하여 어떻게 든 100 % 처리되지 않았기 때문에 nohup을 사용하고 출력을 리디렉션했습니다.


0

긴 스크립트가 있으면 각 작업에 대한 입력 매개 변수의 도움으로 페이지 작업을 나눕니다. (각 페이지는 스레드처럼 작동합니다) 즉, 페이지에 1 개의 lac product_keywords 긴 프로세스 루프가있는 경우 루프 대신 하나의 키워드에 대한 논리를 만들고이 키워드를 전달합니다. magic 또는 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
    ?>

추신 : URL 매개 변수를 루프로 보내려면 다음 답변을 따르십시오 : https://stackoverflow.com/a/41225209/6295712


0

여기에 많은 사람들이 언급했듯이 최선의 방법은 아니지만 도움이 될 수 있습니다.

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

스크립트의 원하는 출력이 웹 페이지가 아닌 일부 처리 인 경우 원하는 솔루션은 간단히 다음과 같이 쉘에서 스크립트를 실행하는 것입니다.

php my_script.php

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.