source

PHP 작업 비동기 실행

goodcode 2022. 12. 25. 16:57
반응형

PHP 작업 비동기 실행

저는 다소 큰 웹 어플리케이션에서 일하고 있으며, 백엔드는 대부분 PHP로 되어 있습니다.코드에는 몇 가지 작업을 완료해야 하는 곳이 있지만, 사용자에게 결과를 기다리게 하고 싶지 않습니다.예를 들어, 새 계정을 만들 때 환영 메일을 보내야 합니다.그러나 '등록 완료' 버튼을 눌렀을 때 이메일을 실제로 보낼 때까지 기다리게 하고 싶지 않습니다.처리를 시작하고 바로 사용자에게 메시지를 보내고 싶습니다.

지금까지 어떤 곳에서는 exec()과 함께 해킹 같은 것을 사용해 왔습니다.기본적으로 다음과 같은 작업을 수행합니다.

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

효과가 있는 것 같긴 한데, 더 좋은 방법이 없을까 해서요.MySQL 테이블에 태스크를 큐잉하는 시스템과 테이블을 1초에 한 번씩 쿼리하고 새로운 태스크를 실행하는 별도의 장기 실행 PHP 스크립트를 작성하는 것을 고려하고 있습니다.또, 필요에 따라서, 장래에 복수의 워커 머신에 작업을 분할할 수 있는 이점도 있습니다.

제가 바퀴를 다시 발명하는 건가요?exec() hack이나 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 접속이 닫혀도, 스크립트는 시간 제한 없이 계속 실행됩니다.

프로세스를 포크하는 또 다른 방법은 컬을 사용하는 것입니다.내부 태스크를 웹 서비스로 설정할 수 있습니다.예를 들어 다음과 같습니다.

다음으로 사용자 접근스크립트에서 서비스에 콜을 발신합니다.

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

서비스에서는 mysql을 사용하여 작업 대기열을 추적할 수 있습니다.즉, 모든 작업은 서비스 내에서 처리되며 스크립트는 URL만 소비합니다.이것에 의해, 필요에 따라서 서비스를 다른 머신/서버로 이행할 수 있습니다(즉, 간단하게 확장할 수 있습니다).

http 인증 또는 커스텀 인증 스킴(Amazon의 웹 서비스 등)을 추가하면 다른 사람/서비스에 의해 소비되는 태스크를 오픈할 수 있습니다(원하는 경우). 더 나아가 큐와 태스크 상태를 추적하기 위해 모니터링 서비스를 추가할 수 있습니다.

셋업 작업이 조금 걸리긴 하지만 많은 이점이 있습니다.

php-fpm을 지원하는 경우, 고가의 작업을 제공한다는 질문만 있다면 왜 기능을 사용하지 않는가?

이 함수는 모든 응답 데이터를 클라이언트에 플러시하고 요청을 완료합니다.이것에 의해, 클라이언트에의 접속을 열어 두지 않고, 시간이 걸리는 작업을 실행할 수 있습니다.

비동기성은 실제로 다음과 같은 방식으로 사용하지 않습니다.

  1. 먼저 모든 기본 코드를 만드십시오.
  2. ★★fastcgi_finish_request().
  3. 무거운 거 다 만들어.

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

나는 한 프로젝트에 Beanstalkd를 사용했고, 다시 그럴 계획을 세웠습니다.비동기 프로세스를 실행하는 데 탁월한 방법이라는 것을 알게 되었습니다.

이 기능을 사용하여 몇 가지 작업을 수행했습니다.

  • 이미지 크기 조정 - CLI 기반의 PHP 스크립트에 가볍게 로드된 큐를 사용하여 큰(2MB 이상) 이미지의 크기를 조정하면 문제없이 작동하지만 mod_php 인스턴스 내에서 동일한 이미지의 크기를 조정하려고 하면 메모리 공간 문제가 정기적으로 발생합니다(PHP 프로세스를 32MB로 제한하고 크기 조정에 더 많은 시간이 소요되었습니다).
  • 가까운 장래의 체크 - beanstalkd에 이용 가능한 지연이 있습니다(이 작업은 X초 후에만 실행할 수 있습니다).따라서 이벤트에 대한 체크를 5개 또는 10개 실행할 수 있습니다.조금 늦게 시작할 수 있습니다.

들어에 Zend-Framework를 "nice" URL이라고 . 예를 들어, 이미지 크기를 조정하면 호출됩니다.QueueTask('/image/resize/filename/example.jpg')URL(module, controller, action, parameters)은 JSON으로 지정합니다.

그 후 장시간 실행되는 CLI 스크립트가 큐에서 작업을 픽업하여 (Zend_Router_Simple을 통해) 실행하고 필요에 따라 웹 사이트 PHP가 픽업할 수 있도록 정보를 memcached에 넣습니다.

또한 cli-script는 재시작 전에 50루프만 실행되지만 계획대로 재시작할 경우 즉시 재시작합니다(bash-script를 통해 실행).가 때 가 했을 때exit(0): (''))exit; ★★★★★★★★★★★★★★★★★」die();에 몇합니다.) , , , , , , , , , , , , , , , .

여기 웹 어플리케이션용으로 코드화한 간단한 클래스가 있습니다.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 HAS 멀티스레딩은 디폴트로 활성화되지 않습니다.pthreads라고 불리는 확장이 있습니다.단, ZTS를 사용하여 php를 컴파일해야 합니다.(스레드 세이프) 링크:

다른 튜토리얼

pthreads PECL 확장

업데이트: PHP 7.2 병렬 확장이 적용되기 때문에

튜토리얼/예시

참조 매뉴얼

이것은 제가 몇 년 동안 사용해 온 것과 같은 방법으로, 지금까지 보다 좋은 것을 보거나 발견하지 못했습니다.사람들이 말했듯이, PHP는 싱글 스레드이기 때문에 당신이 할 수 있는 일은 많지 않습니다.

여기에 1레벨을 추가했습니다.그것은 프로세스 ID의 취득과 저장입니다.이를 통해 다른 페이지로 리디렉션하여 AJAX를 사용하여 프로세스 완료 여부(프로세스 ID가 더 이상 존재하지 않음)를 확인할 수 있습니다.이는 스크립트의 길이로 인해 브라우저가 타임아웃될 수 있지만 사용자는 다음 단계까지 스크립트가 완료될 때까지 기다릴 필요가 있습니다(이 경우 CSV와 같은 파일을 사용하여 데이터베이스에 최대 30,000개의 레코드를 추가하는 대용량 ZIP 파일을 처리하고 있었습니다).이후 사용자는 정보를 확인해야 합니다.

저도 비슷한 방법으로 보고서를 작성했습니다.SMTP의 속도가 느린 것이 문제가 되지 않는 한, E-메일등의 것에 「백그라운드 처리」를 사용할지 어떨지 잘 모르겠습니다.대신 테이블을 큐로 사용하고 큐 내에서 이메일을 보내기 위해 매분 실행되는 프로세스를 수행할 수 있습니다.이메일을 두 번 보내거나 다른 비슷한 문제를 일으킬 경우 주의해야 합니다.다른 작업에서도 같은 큐잉 프로세스를 고려하겠습니다.

rojoca의 제안대로 cURL을 사용하는 것은 좋은 생각입니다.

여기 예가 있습니다.텍스트를 모니터할 수 있습니다.스크립트가 백그라운드에서 실행되는 동안 txt:

<?php

function doCurl($begin)
{
    echo "Do curl<br />\n";
    $url = 'http://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
    $url = preg_replace('/\?.*/', '', $url);
    $url .= '?begin='.$begin;
    echo 'URL: '.$url.'<br>';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    echo 'Result: '.$result.'<br>';
    curl_close($ch);
}


if (empty($_GET['begin'])) {
    doCurl(1);
}
else {
    while (ob_get_level())
        ob_end_clean();
    header('Connection: close');
    ignore_user_abort();
    ob_start();
    echo 'Connection Closed';
    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush();
    flush();

    $begin = $_GET['begin'];
    $fp = fopen("text.txt", "w");
    fprintf($fp, "begin: %d\n", $begin);
    for ($i = 0; $i < 15; $i++) {
        sleep(1);
        fprintf($fp, "i: %d\n", $i);
    }
    fclose($fp);
    if ($begin < 10)
        doCurl($begin + 1);
}

?>

Swool이라는 PHP 확장자가 있습니다.

활성화 되어 있지 않을 수도 있지만 버튼을 클릭하기만 하면 활성화 되어 호스팅에서 사용할 수 있습니다.

확인해 볼 만합니다.아직 쓸 시간이 없었어요.여기서 정보를 찾다가 우연히 발견해서 공유할 가치가 있다고 생각했어요.

안타깝게도 PHP에는 네이티브 스레드 기능이 없습니다.따라서 이 경우 원하는 작업을 수행하기 위해 일종의 사용자 지정 코드를 사용할 수밖에 없다고 생각합니다.

인터넷상에서 PHP 스레드화를 검색하면 PHP에서 스레드를 시뮬레이트할 수 있는 방법을 생각해 낸 사람도 있습니다.

"등록해 주셔서 감사합니다" 응답에서 Content-Length HTTP 헤더를 설정한 경우 브라우저는 지정된 바이트 수를 수신한 후 연결을 닫아야 합니다.이것에 의해, 서버측 프로세스는(ignore_user_abort 가 설정되어 있는 것을 전제로 하고 있습니다), 최종 유저를 기다리게 하지 않고 작업을 완료할 수 있습니다.

물론 헤더를 렌더링하기 전에 응답 내용의 크기를 계산해야 하지만, 짧은 응답(문자열에 대한 쓰기 출력, call strlen(), call header(), render string)에 대해서는 매우 간단합니다.

이 접근방식은 "프런트 엔드" 큐를 관리하도록 강요하지 않는다는 장점이 있습니다.또한 HTTP 자녀 프로세스가 서로 경합하는 것을 방지하기 위해 백엔드에서 몇 가지 작업을 수행해야 할 수도 있지만 이는 이미 수행해야 할 작업입니다.

ActiveMQ를 풀로 하고 싶지 않다면 RabbitMQ를 고려해 볼 것을 권장합니다.Rabbit MQMQ는 AMQP 표준을 사용하는 경량 메시징입니다.

AMQP 기반 메시지 브로커에 접속하기 위해 널리 사용되는 AMQP 클라이언트 라이브러리인 php-amqplib도 살펴볼 것을 권장합니다.

이 기술을 사용해 보는 것이 좋을 것 같습니다.모든 페이지가 비동기 페이지 응답을 기다리지 않고 동시에 실행할 수 있는 페이지 수만큼 호출하는 것이 도움이 됩니다.

콘잡 페이지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);
            }
            ?>

테스트 페이지php

    <?
    echo $_REQUEST["Keywordname"];//case1 Output > testValue
    ?>

PS: URL 파라미터를 루프로 송신하는 경우는, 다음의 회답에 따릅니다.https://stackoverflow.com/a/41225209/6295712

를 사용한 exec()또는 컬을 사용하여 직접 다른 서버에 접속해도 확장이 잘 되지 않습니다.exec를 선택하면 기본적으로 웹 이외의 다른 서버에서 처리할 수 있는 장시간 실행 프로세스가 서버에 가득 차게 됩니다.또한 로드밸런싱을 구축하지 않는 한 컬타이를 사용하여 다른 서버를 가동시킵니다.

Gearman을 몇 가지 상황에서 사용해 본 적이 있으며, 이러한 사용 사례에 더 적합합니다.단일 작업 큐 서버를 사용하여 기본적으로 서버에서 수행해야 하는 모든 작업의 큐잉을 처리하고 작업자 서버를 스핀업할 수 있습니다. 각 작업은 작업자 프로세스의 인스턴스를 필요한 만큼 실행할 수 있으며 작업자 서버의 수를 필요에 따라 확장하거나 필요하지 않을 때 스핀다운할 수 있습니다.또한 필요에 따라 작업자 프로세스를 완전히 종료하고 작업자가 다시 온라인 상태가 될 때까지 작업을 대기열에 넣을 수도 있습니다.

언어이기 에 PHP를 사용하는 것 .exec또는popen여기에 그것에 대한 블로그 투고가 있어요.MySQL의 큐에 대한 당신의 아이디어도 좋습니다.

여기서의 요건은, 유저에게 전자 메일을 송신하는 것입니다.메일을 보내는 것은 매우 간단하고 빠른 작업인데, 왜 비동기식으로 하려고 하는지 궁금합니다.메일을 대량으로 보내고 있고, ISP가 스팸 발송의 혐의로 당신을 차단하고 있다면, 그것이 큐잉을 해야 하는 이유 중 하나일 수 있지만, 그 외에는 이렇게 해야 할 이유가 생각나지 않습니다.

언급URL : https://stackoverflow.com/questions/858883/run-php-task-asynchronously

반응형