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의 웹 서비스)를 추가하면 다른 사람 / 서비스 (원하는 경우)가 소비 할 작업을 열 수 있으며 계속 추적하여 모니터링 서비스를 추가 할 수 있습니다. 대기열 및 작업 상태.
- http : // domain / queue? task = t1
- http : // domain / queue? task = t2
- http : // domain / queue / t1 / 100931
약간의 설정 작업이 필요하지만 많은 이점이 있습니다.
한 프로젝트에 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()기능 을 사용하지 않는 이유는 무엇입니까?
이 함수는 모든 응답 데이터를 클라이언트로 플러시하고 요청을 완료합니다. 이를 통해 클라이언트 연결을 열어 두지 않고 시간이 많이 걸리는 작업을 수행 할 수 있습니다.
이 방법으로 비동기 성을 실제로 사용하지는 않습니다.
- 모든 주요 코드를 먼저 만드십시오.
- 실행합니다
fastcgi_finish_request(). - 모든 무거운 물건을 만드십시오.
다시 한번 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는 단일 스레드이므로 할 수있는 일이별로 없습니다.
I have actually added one extra level to this and that's getting and storing the process id. This allows me to redirect to another page and have the user sit on that page, using AJAX to check if the process is complete (process id no longer exists). This is useful for cases where the length of the script would cause the browser to timeout, but the user needs to wait for that script to complete before the next step. (In my case it was processing large ZIP files with CSV like files that add up to 30 000 records to the database after which the user needs to confirm some information.)
I have also used a similar process for report generation. I'm not sure I'd use "background processing" for something such as an email, unless there is a real problem with a slow SMTP. Instead I might use a table as a queue and then have a process that runs every minute to send the emails within the queue. You would need to be warry of sending emails twice or other similar problems. I would consider a similar queueing process for other tasks as well.
PHP HAS multithreading, its just not enabled by default, there is an extension called pthreads which does exactly that. You'll need php compiled with ZTS though. (Thread Safe) Links:
It's a great idea to use cURL as suggested by rojoca.
Here is an example. You can monitor text.txt while the script is running in background:
<?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);
}
?>
Unfortunately PHP does not have any kind of native threading capabilities. So I think in this case you have no choice but to use some kind of custom code to do what you want to do.
If you search around the net for PHP threading stuff, some people have come up with ways to simulate threads on PHP.
If you set the Content-Length HTTP header in your "Thank You For Registering" response, then the browser should close the connection after the specified number of bytes are received. This leaves the server side process running (assuming that ignore_user_abort is set) so it can finish working without making the end user wait.
Of course you will need to calculate the size of your response content before rendering the headers, but that's pretty easy for short responses (write output to a string, call strlen(), call header(), render string).
This approach has the advantage of not forcing you to manage a "front end" queue, and although you may need to do some work on the back end to prevent racing HTTP child processes from stepping on each other, that's something you needed to do already, anyway.
If you don't want the full blown ActiveMQ, I recommend to consider RabbitMQ. RabbitMQ is lightweight messaging that uses the AMQP standard.
I recommend to also look into php-amqplib - a popular AMQP client library to access AMQP based message brokers.
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
Spawning new processes on the server using exec() or directly on another server using curl doesn't scale all that well at all, if we go for exec you are basically filling your server with long running processes which can be handled by other non web facing servers, and using curl ties up another server unless you build in some sort of load balancing.
I have used Gearman in a few situations and I find it better for this sort of use case. I can use a single job queue server to basically handle queuing of all the jobs needing to be done by the server and spin up worker servers, each of which can run as many instances of the worker process as needed, and scale up the number of worker servers as needed and spin them down when not needed. It also let's me shut down the worker processes entirely when needed and queues the jobs up until the workers come back online.
PHP is a single-threaded language, so there is no official way to start an asynchronous process with it other than using exec or popen. There is a blog post about that here. Your idea for a queue in MySQL is a good idea as well.
Your specific requirement here is for sending an email to the user. I'm curious as to why you are trying to do that asynchronously since sending an email is a pretty trivial and quick task to perform. I suppose if you are sending tons of email and your ISP is blocking you on suspicion of spamming, that might be one reason to queue, but other than that I can't think of any reason to do it this way.
참고URL : https://stackoverflow.com/questions/858883/run-php-task-asynchronously
'Programming' 카테고리의 다른 글
| #pragma는 한 번 C ++ 11 표준의 일부입니까? (0) | 2020.06.24 |
|---|---|
| 널 입력 가능 ToString () (0) | 2020.06.24 |
| 컴파일 할 때 -pthread 플래그의 의미 (0) | 2020.06.24 |
| 초과 비 실행 Docker 컨테이너에 대해 걱정해야합니까? (0) | 2020.06.24 |
| java.net.SocketException의 원인 : 연결 재설정? (0) | 2020.06.24 |