[php] PHP 작업을 비동기 적으로 실행

다소 큰 웹 응용 프로그램에서 작업하고 있으며 백엔드는 대부분 PHP입니다. 코드에 몇 가지 작업을 완료 해야하는 곳이 있지만 사용자가 결과를 기다리도록하고 싶지 않습니다. 예를 들어, 새 계정을 만들 때 환영 이메일을 보내야합니다. 그러나 ‘등록 완료’버튼을 누르면 이메일이 실제로 전송 될 때까지 기다리지 않고 프로세스를 시작하고 사용자에게 메시지를 즉시 반환하고 싶습니다.

지금까지 어떤 곳에서는 exec ()와 같은 느낌의 것을 사용했습니다. 기본적으로 다음과 같은 작업을 수행합니다.

exec("doTask.php $arg1 $arg2 $arg3 >/dev/null 2>&1 &");

어느 것이 효과가있는 것처럼 보이지만 더 좋은 방법이 있는지 궁금합니다. MySQL 테이블에 작업을 대기열에 넣는 시스템과 해당 테이블을 1 초에 한 번 쿼리하고 찾은 새로운 작업을 실행하는 별도의 장기 실행 PHP 스크립트를 작성하려고합니다. 또한 필요한 경우 나중에 여러 작업자 시스템간에 작업을 분할 할 수 있다는 이점이 있습니다.

바퀴를 다시 발명하고 있습니까? exec () 해킹 또는 MySQL 대기열보다 더 나은 솔루션이 있습니까?



답변

큐 접근 방식을 사용했으며 서버로드가 유휴 상태가 될 때까지 해당 처리를 연기 할 수있어 “긴급하지 않은 작업”을 쉽게 분할 할 수 있으면로드를 매우 효과적으로 관리 할 수 ​​있습니다.

직접 롤링하는 것은 그리 까다 롭지 않습니다. 다음은 확인할 다른 옵션입니다.

  • GearMan- 이 답변은 2009 년에 작성되었으며 그 이후로 GearMan은 인기있는 옵션으로 보입니다. 아래 설명을 참조하십시오.
  • 전체 오픈 소스 메시지 큐를 원하는 경우 ActiveMQ
  • ZeroMQ- 소켓 프로그래밍 자체에 대해 너무 걱정할 필요없이 분산 코드를 쉽게 작성할 수있는 매우 멋진 소켓 라이브러리입니다. 단일 호스트에서 메시지 대기열에 사용할 수 있습니다. 웹앱이 다음 번에 적절한 기회에 지속적으로 실행되는 콘솔 앱이 소비하는 대기열로 무언가를 밀어 넣습니다.
  • beanstalkd- 이 답변을 작성하는 동안 만 찾았지만 흥미로워 보입니다.
  • dropr 는 PHP 기반의 메시지 큐 프로젝트이지만 2010 년 9 월 이후 적극적으로 관리되지 않았습니다
  • php-enqueue 는 다양한 대기열 시스템을 중심으로 최근 (2017) 유지 관리 래퍼입니다.
  • 마지막으로 메시지 대기열에 memcached를 사용하는 방법에 대한 블로그 게시물

또 다른 방법은 ignore_user_abort 를 사용 하는 것입니다. 페이지를 사용자에게 보낸 후에는 조기 종료에 대한 두려움없이 최종 처리를 수행 할 수 있습니다. 원근법.


답변

응답을 기다리지 않고 하나 이상의 HTTP 요청을 실행하려는 경우 간단한 PHP 솔루션도 있습니다.

호출 스크립트에서 :

$socketcon = fsockopen($host, 80, $errno, $errstr, 10);
if($socketcon) {
   $socketdata = "GET $remote_house/script.php?parameters=... HTTP 1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n";
   fwrite($socketcon, $socketdata);
   fclose($socketcon);
}
// repeat this with different parameters as often as you like

호출 된 script.php에서 첫 번째 행에서 다음 PHP 함수를 호출 할 수 있습니다.

ignore_user_abort(true);
set_time_limit(0);

이로 인해 HTTP 연결이 닫힐 때 스크립트가 시간 제한없이 계속 실행됩니다.


답변

프로세스를 분기하는 또 다른 방법은 curl을 사용하는 것입니다. 내부 작업을 웹 서비스로 설정할 수 있습니다. 예를 들면 다음과 같습니다.

그런 다음 사용자 액세스 스크립트에서 서비스를 호출하십시오.

$service->addTask('t1', $data); // post data to URL via curl

귀하의 서비스는 mysql 또는 원하는 점이 무엇이든간에 작업 대기열을 추적 할 수 있습니다 : 서비스 내에 모두 포함되어 있으며 스크립트는 URL을 소비하고 있습니다. 이렇게하면 필요한 경우 서비스를 다른 머신 / 서버로 옮길 수 있습니다 (즉, 쉽게 확장 가능).

http 권한 또는 사용자 지정 권한 부여 체계 (예 : Amazon의 웹 서비스)를 추가하면 다른 사람 / 서비스 (원하는 경우)가 소비 할 작업을 열 수 있으며 계속 추적하여 모니터링 서비스를 추가 할 수 있습니다. 대기열 및 작업 상태.

약간의 설정 작업이 필요하지만 많은 이점이 있습니다.


답변

한 프로젝트에 Beanstalkd 를 사용 하고 다시 계획했습니다. 비동기 프로세스를 실행하는 훌륭한 방법이라는 것을 알았습니다.

내가 한 두 가지 작업은 다음과 같습니다.

  • 이미지 크기 조정-약간의 대기열이 CLI 기반 PHP 스크립트로 전달되어 큰 (2mb +) 이미지 크기 조정은 정상적으로 작동했지만 mod_php 인스턴스 내에서 동일한 이미지의 크기를 조정하려고 시도하면 정기적으로 메모리 공간 문제가 발생했습니다 (I PHP 프로세스를 32MB로 제한했으며 크기 조정은 그 이상이었습니다.
  • 가까운 미래 점검-Beanstalkd에서 지연을 사용할 수 있습니다 (X 초 후에 만이 작업을 실행 가능하게 함). 이벤트에 대해 5 ~ 10 회 점검을 실행할 수 있습니다.

‘nice’url을 해독하기 위해 Zend-Framework 기반 시스템을 작성했습니다. 예를 들어 이미지 크기를 조정하려면 QueueTask('/image/resize/filename/example.jpg'). URL은 먼저 배열 (모듈, 컨트롤러, 작업, 매개 변수)로 디코딩 된 다음 대기열 자체에 주입하기 위해 JSON으로 변환되었습니다.

오랫동안 실행되는 cli 스크립트는 대기열에서 작업을 가져 와서 (Zend_Router_Simple을 통해) 실행 한 다음, 필요한 경우 웹 사이트 PHP가 정보를 수집 할 때 memcached에 정보를 입력하여 완료되었습니다.

필자가 넣은 한 가지 주름은 cli-script가 다시 시작하기 전에 50 루프 동안 만 실행되었지만 계획대로 다시 시작하려는 경우 즉시 시작합니다 (bash-script를 통해 실행 됨). 문제가 있고 내가 한 경우 exit(0)( exit;또는 의 기본값 die();) 먼저 몇 초 동안 일시 중지됩니다.


답변

비싼 작업을 제공 해야하는 문제라면 php-fpm이 지원되는 경우 fastcgi_finish_request()기능 을 사용하지 않는 이유는 무엇입니까?

이 함수는 모든 응답 데이터를 클라이언트로 플러시하고 요청을 완료합니다. 이를 통해 클라이언트 연결을 열어 두지 않고 시간이 많이 걸리는 작업을 수행 할 수 있습니다.

이 방법으로 비동기 성을 실제로 사용하지는 않습니다.

  1. 모든 주요 코드를 먼저 만드십시오.
  2. 실행합니다 fastcgi_finish_request().
  3. 모든 무거운 물건을 만드십시오.

다시 한번 php-fpm이 필요합니다.


답변

다음은 웹 응용 프로그램을 위해 코딩 한 간단한 클래스입니다. PHP 스크립트 및 기타 스크립트를 포크 할 수 있습니다. UNIX 및 Windows에서 작동합니다.

class BackgroundProcess {
    static function open($exec, $cwd = null) {
        if (!is_string($cwd)) {
            $cwd = @getcwd();
        }

        @chdir($cwd);

        if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
            $WshShell = new COM("WScript.Shell");
            $WshShell->CurrentDirectory = str_replace('/', '\\', $cwd);
            $WshShell->Run($exec, 0, false);
        } else {
            exec($exec . " > /dev/null 2>&1 &");
        }
    }

    static function fork($phpScript, $phpExec = null) {
        $cwd = dirname($phpScript);

        @putenv("PHP_FORCECLI=true");

        if (!is_string($phpExec) || !file_exists($phpExec)) {
            if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
                $phpExec = str_replace('/', '\\', dirname(ini_get('extension_dir'))) . '\php.exe';

                if (@file_exists($phpExec)) {
                    BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
                }
            } else {
                $phpExec = exec("which php-cli");

                if ($phpExec[0] != '/') {
                    $phpExec = exec("which php");
                }

                if ($phpExec[0] == '/') {
                    BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
                }
            }
        } else {
            if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
                $phpExec = str_replace('/', '\\', $phpExec);
            }

            BackgroundProcess::open(escapeshellarg($phpExec) . " " . escapeshellarg($phpScript), $cwd);
        }
    }
}


답변

이것은 내가 몇 년 동안 사용해 왔던 것과 같은 방법이며 더 나은 것을 보거나 찾지 못했습니다. 사람들이 말했듯이, PHP는 단일 스레드이므로 할 수있는 일이별로 없습니다.

실제로 이것에 하나의 추가 레벨을 추가했으며 프로세스 ID를 가져오고 저장합니다. 이를 통해 다른 페이지로 리디렉션하고 사용자가 해당 페이지에 앉아 AJAX를 사용하여 프로세스가 완료되었는지 확인합니다 (프로세스 ID가 더 이상 존재하지 않음). 이는 스크립트 길이로 인해 브라우저가 시간 초과 될 수 있지만 다음 단계 전에 해당 스크립트가 완료 될 때까지 기다려야하는 경우에 유용합니다. (제 경우에는 사용자가 정보를 확인 해야하는 데이터베이스에 최대 30 000 개의 레코드를 추가하는 CSV와 같은 파일로 큰 ZIP 파일을 처리하고있었습니다.)

보고서 생성에도 비슷한 프로세스를 사용했습니다. 느린 SMTP에 실제 문제가없는 한 전자 메일과 같은 작업에 “백그라운드 처리”를 사용할지 잘 모르겠습니다. 대신 테이블을 대기열로 사용한 다음 1 분마다 실행되는 프로세스를 통해 대기열 내에서 전자 메일을 보낼 수 있습니다. 이메일을 두 번 또는 다른 유사한 문제로 보내는 것에주의해야합니다. 다른 작업에도 비슷한 큐잉 프로세스를 고려할 것입니다.