Bir form gönderildikten sonra bir PHP betiğini arka planda nasıl çalıştırabilirim?


105

Sorun
Gönderildiğinde, gönderilen bilgileri işlemek için temel kodu çalıştıracak ve bir bildirim web sitesinde görüntülenmek üzere bir veritabanına ekleyecek bir formum var. Ek olarak, bu bildirimleri e-posta ve SMS mesajıyla almak için kaydolan kişilerin bir listesi var. Bu liste şu an için önemsizdir (yalnızca 150'yi iter), ancak tüm abone tablosunda dolaşıp 150'den fazla e-posta göndermenin bir dakikadan fazla sürmesine neden olmak yeterlidir. (E-postalar, toplu e-posta politikaları nedeniyle e-posta sunucumuzun sistem yöneticileri tarafından talep edildiği şekilde ayrı ayrı gönderilir.)

Bu süre zarfında, uyarıyı gönderen kişi, bildiriminin yayınlandığına dair herhangi bir olumlu destek olmadan formun son sayfasında neredeyse bir dakika oturacaktır. Bu, başka olası sorunlara yol açar, tüm olası çözümlere sahip olanların idealden daha az olduğunu düşünürüm.

  1. İlk olarak, poster sunucunun geciktiğini düşünebilir ve 'Gönder' düğmesini tekrar tıklayarak komut dosyasının yeniden başlamasına veya iki kez çalışmasına neden olabilir. Bunu, düğmeyi devre dışı bırakmak için JavaScript kullanarak ve metni 'İşleniyor ...' gibi bir şey söyleyecek şekilde değiştirerek çözebilirim, ancak bu idealden daha az çünkü kullanıcı, komut dosyası yürütme süresi boyunca sayfada kalmaya devam edecek. (Ayrıca, JavaScript devre dışı bırakılmışsa, bu sorun hala mevcuttur.)

  2. İkinci olarak, formu gönderdikten sonra poster sekmeyi veya tarayıcıyı erken kapatabilir. Komut dosyası, tarayıcıya yeniden yazmaya çalışıncaya kadar sunucuda çalışmaya devam edecektir, ancak kullanıcı alanımızdaki herhangi bir sayfaya göz atarsa ​​(komut dosyası hala çalışırken), tarayıcı komut dosyası bitene kadar sayfayı yüklemeyi askıya alır. . (Bu, yalnızca tarayıcının bir sekmesi veya penceresi kapatıldığında ve tarayıcı uygulamasının tamamı kapalı olduğunda olur.) Yine de, bu idealden daha azdır.

(Olası) Çözüm
Komut dosyasının "e-posta" kısmını, bildirim gönderildikten sonra arayabileceğim ayrı bir dosyaya bölmeye karar verdim. Başlangıçta bunu, bildirim başarıyla gönderildikten sonra onay sayfasına koymayı düşündüm. Ancak, kullanıcı bu betiğin çalıştığını bilmeyecek ve herhangi bir anormallik onlara görünmeyecektir; Bu komut dosyası başarısız olamaz.

Peki ya bu komut dosyasını bir arka plan işlemi olarak çalıştırabilirsem? Öyleyse sorum şu: Bir PHP betiğini bir arka plan hizmeti olarak tetiklemek ve kullanıcının form düzeyinde yaptıklarından tamamen bağımsız olarak çalıştırmak için nasıl çalıştırabilirim?

DÜZENLEME: Bu olamaz cron'ed edilecek. Form gönderildiği anda çalıştırılmalıdır. Bunlar yüksek öncelikli bildirimlerdir. Ayrıca, sunucularımızı çalıştıran sistem yöneticileri, cron'ların 5 dakikadan daha sık çalışmasına izin vermez.


Bir cron işi başlatın ve kullanıcıyı başka bir sayfada isteğinin işlenmekte olduğu veya buna benzer bir şey konusunda uyarın.
Evan Mulawski

1
Düzenli olarak mı (her gün veya her saat) yoksa teslim sırasında mı çalıştırmak istiyorsunuz? Sanırım php kullanabilirsiniz exec()ama bunu hiç denemedim.
Simon

2
Arka plan işlemini çalıştırmak için döngü içinde zaman aralığı ile kullanıyorum ajaxve ekliyorum sleep()(benim durumumda foreach kullanıyorum). Sunucumda mükemmel çalışıyor. Başkaları hakkında emin değilim. Ayrıca testime göre komut dosyasının ve olmadan görevi tamamlayan komut dosyasının kullandığım sunucu tarafından durdurulmamasını sağlamak için döngüden önce ignore_user_abort()ve set_time_limit()(hesaplanan bitiş zamanıyla) ekliyorum . ignore_user_abort()set_time_limit()
Ari

Zaten bir çözüm bulduğunu gördüm. gearmanArka plan görevleriyle uğraşmak için php ile kullanıyorum . Büyük zamanlı işleme ve ağır yüklerle karşılaşırsanız ölçeklendirmek mükemmeldir. Ona bir bakmalısın.
Andre Garcia

Bu yanıtı [stackoverflow] 'da ( stackoverflow.com/a/46617107/6417678 ) takip edin
Joshy Francis

Yanıtlar:


89

Üzerinde biraz deneyler yaptım execve shell_execmükemmel çalışan bir çözümü ortaya çıkardım! shell_execOlan (veya olmayan) her bildirim işlemini günlüğe kaydedebilmek için kullanmayı seçiyorum . ( shell_execbir dizge olarak döner ve bu kullanmaktan exec, çıktıyı bir değişkene atamaktan ve sonra yazmak için bir dosyayı açmaktan daha kolaydı .)

E-posta komut dosyasını çağırmak için aşağıdaki satırı kullanıyorum:

shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");

Fark önemlidir &(@netcoder tarafından sivri out gibi) komutunun sonunda. Bu UNIX komutu arka planda bir işlem çalıştırır.

Komut dosyasının yolundan sonra tek tırnak içine alınmış ekstra değişkenler, komut $_SERVER['argv']dosyamda çağırabileceğim değişkenler olarak ayarlandı .

E-posta betiği daha sonra günlük dosyama şunu kullanarak >>çıktı verir ve şöyle bir çıktı verir:

[2011-01-07 11:01:26] Alert Notifications Sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 38.71 seconds)
[2011-01-07 11:01:34] CRITICAL ERROR: Alert Notifications NOT sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 23.12 seconds)

Teşekkürler. Bu hayatımı kurtardı.
Liam Bailey 13

$post_idPhp betiğinin yolundan sonraki nedir ?
Perocat

@Perocat betiğe gönderdiğiniz bir değişkendir. Bu durumda, çağrılan betiğe bir parametre olacak bir kimlik gönderiyor.
Chique

Bunun mükemmel şekilde çalıştığından şüpheliyim, bkz. Stackoverflow.com/questions/2212635/…
symcbean

1
Çıkmak için bekleyen bir düzenleme önerisi var $post_id; Daha doğrusu bir numaraya doğrudan dökme olacaktır: (int) $post_id. (Hangisinin daha iyi olduğuna karar vermek için dikkatinizi
çekeriz

19

Linux / Unix sunucularında proc_open kullanarak arka planda bir iş yürütebilirsiniz :

$descriptorspec = array(
   array('pipe', 'r'),               // stdin
   array('file', 'myfile.txt', 'a'), // stdout
   array('pipe', 'w'),               // stderr
);

$proc = proc_open('php email_script.php &', $descriptorspec, $pipes);

&Önemli biraz burada olmak. Orijinal komut dosyası bitmiş olsa bile komut dosyası devam edecektir.


Bu proc_close, görev tamamlandığında aramadığımda çalışıyor. proc_closebetiğin yaklaşık 15 ila 25 saniye beklemesini sağlar. Boru bilgisini kullanarak bir günlük tutmam gerekecek, bu yüzden yazılacak bir dosyayı açmam, onu kapatmam ve ardından proc bağlantısını kapatmam gerekiyor. Yani maalesef bu çözüm bende işe yaramıyor.
Michael Irigoyen

3
Tabii ki aramıyorsun proc_close, mesele bu! Ve evet, ile bir dosyaya aktarabilirsiniz proc_open. Güncellenen yanıta bakın.
netcoder

@netcoder: sonucu herhangi bir dosyada saklamak istemiyorsam ne olur? stdout'ta hangi değişiklikler gereklidir?
naggarwal11

9

Tüm yanıtlar arasında, hiç kimse gülünç derecede kolay fastcgi_finish_request işlevini düşünmedi ; çağrıldığında, kalan tüm çıktıları tarayıcıya temizler ve betiğin arka planda çalışmasına izin verirken Fastcgi oturumunu ve HTTP bağlantısını kapatır.

Bir örnek:

<?php
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
fastcgi_finish_request(); // The user is now disconnected from the script

// do stuff with received data,

bu tam olarak aradığım şeydi, shell_exec'te yayınlanan veriyi bir şekilde yürütülen betiğe aktarmam gerekiyor ki bu benim durumumda zaman alıcı bir görev ve aynı zamanda işleri çok karmaşık hale getiriyor.
Aurangzeb

7

PHP exec("php script.php")bunu yapabilir.

Gönderen Manuel :

Bir program bu işlevle başlatılırsa, arka planda çalışmaya devam edebilmesi için, programın çıktısının bir dosyaya veya başka bir çıktı akışına yeniden yönlendirilmesi gerekir. Bunu yapmamak, PHP'nin programın çalışması sona erene kadar askıda kalmasına neden olur.

Bu nedenle, çıktıyı bir günlük dosyasına yeniden yönlendirirseniz (her halükarda iyi bir fikirdir), çağıran komut dosyanız takılmaz ve e-posta komut dosyanız bg olarak çalışır.


1
Teşekkürler ama bu işe yaramadı. Komut dosyası, ikinci dosyayı işlerken hala "kilitleniyor".
Michael Irigoyen

Php.net/manual/en/function.exec.php#80582 açıklamasına bakın, Safe_mode'un etkin olmasından kaynaklanıyor gibi görünüyor. Unix altında bir çözüm var.
Simon

Ne yazık ki, kurulumumuzda güvenli mod etkin değil. Hala asılı olması tuhaf.
Michael Irigoyen

6

Ve neden komut dosyası üzerinde bir HTTP İsteği yapıp yanıtı görmezden gelmiyorsunuz?

http://php.net/manual/en/function.httprequest-send.php

Komut dosyası üzerinde istekte bulunursanız, web sunucunuzu aramanız gerekir ve (ana komut dosyanızda) kullanıcıya komut dosyasının çalıştığını bildiren bir mesaj gösterebilirsiniz.


4

Buna ne dersin?

  1. Formu tutan PHP betiğiniz bir bayrağı veya bir değeri bir veritabanına veya dosyaya kaydeder.
  2. İkinci bir PHP betiği bu değeri düzenli olarak sorgular ve eğer ayarlanmışsa, E-posta betiğini eşzamanlı bir şekilde tetikler.

Bu ikinci PHP betiği bir cron olarak çalışacak şekilde ayarlanmalıdır.


Teşekkürler, ama asıl soruyu güncelledim. Bu durumda Cron'lar bir seçenek değildir.
Michael Irigoyen

4

Bunu kolay bir şekilde yapamayacağınızı bildiğim gibi (fork exec vb. (Pencerelerin altında çalışmayın)), yaklaşımı tersine çevirebilir, formu ajax'a gönderen tarayıcının arka planını kullanabilirsiniz, bu nedenle gönderi hala iş bekleme süren yok.
Uzun bir detaylandırma yapmanız gerekse bile bu yardımcı olabilir.

Posta göndermekle ilgili olarak, her zaman bir biriktirici kullanılması önerilir, isteklerinizi kabul eden ve bunları gerçek MTA'da biriktiren veya hepsini bir DB'ye yerleştiren yerel ve hızlı bir smtp sunucusu, kuyruğu biriktiren bir cron kullanmaktan daha iyidir.
Cron, biriktiriciyi harici url olarak çağıran başka bir makinede olabilir:

* * * * * wget -O /dev/null http://www.example.com/spooler.php

3

Arka plan cron işi bunun için iyi bir fikir gibi görünüyor.

Komut dosyasını cron olarak çalıştırmak için makineye ssh erişimine ihtiyacınız olacak.

$ php scriptname.php çalıştırmak için.


1
ssh'ye gerek yok. Birçok ana bilgisayar ssh'a izin vermez, ancak cron desteğine sahiptir.
Teson

Teşekkürler, ama asıl soruyu güncelledim. Bu durumda Cron'lar bir seçenek değildir.
Michael Irigoyen

3

Sunucuya ssh üzerinden erişebiliyorsanız ve kendi komut dosyalarınızı çalıştırabiliyorsanız, php kullanarak basit bir fifo sunucusu yapabilirsiniz (bununla birlikte php'yi posixdestekle yeniden derlemeniz gerekecek fork).

Sunucu gerçekten herhangi bir şeyle yazılabilir, muhtemelen python'da kolayca yapabilirsiniz.

Ya da en basit çözüm, bir HttpRequest göndermek ve dönüş verilerini okumamak olabilir, ancak sunucu, işlemeyi bitirmeden önce betiği yok edebilir.

Örnek sunucu:

<?php
define('FIFO_PATH', '/home/user/input.queue');
define('FORK_COUNT', 10);

if(file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' exists, please delete it and try again.' . "\n");
}

if(!file_exists(FIFO_PATH) && !posix_mkfifo(FIFO_PATH, 0666)){
    die('Couldn\'t create the listening fifo.' . "\n");
}

$pids = array();
$fp = fopen(FIFO_PATH, 'r+');
for($i = 0; $i < FORK_COUNT; ++$i) {
    $pids[$i] = pcntl_fork();
    if(!$pids[$i]) {
        echo "process(" . posix_getpid() . ", id=$i)\n";
        while(true) {
            $line = chop(fgets($fp));
            if($line == 'quit' || $line === false) break;
            echo "processing (" . posix_getpid() . ", id=$i) :: $line\n";
        //  $data = json_decode($line);
        //  processData($data);
        }
        exit();
    }
}
fclose($fp);
foreach($pids as $pid){
    pcntl_waitpid($pid, $status);
}
unlink(FIFO_PATH);
?>

Örnek müşteri:

<?php
define('FIFO_PATH', '/home/user/input.queue');
if(!file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' doesn\'t exist, please make sure the fifo server is running.' . "\n");
}

function postToQueue($data) {
    $fp = fopen(FIFO_PATH, 'w+');
    stream_set_blocking($fp, false); //don't block
    $data = json_encode($data) . "\n";
    if(fwrite($fp, $data) != strlen($data)) {
        echo "Couldn't the server might be dead or there's a bug somewhere\n";
    }
    fclose($fp);
}
$i = 1000;
while(--$i) {
    postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>

3

Bir PHP komut dosyasını arka planda çalıştırmanın daha basit yolu

php script.php >/dev/null &

Komut dosyası arka planda çalışacak ve sayfa aynı zamanda işlem sayfasına daha hızlı ulaşacaktır.


2

Bir * nix platformunda çalıştığınızı varsayarsak, cronve phpçalıştırılabilir dosyayı kullanın .

DÜZENLE:

SO'da "php'yi cron olmadan çalıştırmak" için soran pek çok soru var. Işte bir tane:

CRON kullanmadan komut dosyalarını planlayın

Bununla birlikte, yukarıdaki exec () cevabı çok umut verici görünüyor :)


Teşekkürler, ama asıl soruyu güncelledim. Bu durumda Cron'lar bir seçenek değildir.
Michael Irigoyen

2

Windows kullanıyorsanız, araştırın proc_openveya popen...

Ancak, cpanel çalıştıran aynı sunucu "Linux" üzerindeysek, bu doğru yaklaşımdır:

#!/usr/bin/php 
<?php
$pid = shell_exec("nohup nice php -f            
'path/to/your/script.php' /dev/null 2>&1 & echo $!");
While(exec("ps $pid"))
{ //you can also have a streamer here like fprintf,        
 // or fgets
}
?>

Kullanmayın fork()veya curlbunları idare edebileceğinizden şüpheniz varsa, bu sunucunuzu kötüye kullanmak gibi

Son olarak, script.phpyukarıda çağrılan dosyaya şunu yazdığınızdan emin olun:

<?php
ignore_user_abort(TRUE);
set_time_limit(0);
ob_start();
// <-- really optional but this is pure php

//Code to be tested on background

ob_flush(); flush(); 
//this two do the output process if you need some.        
//then to make all the logic possible


str_repeat(" ",1500); 
//.for progress bars or loading images

sleep(2); //standard limit

?>

Daha sonra cep telefonumu kullandığım için düzenlediğim için özür dilerim ve gerçekten de exec () komut dosyası yazmaktan çok daha zor. Lol;)
ben ArbZ

2

arkaplan çalışanı için bu tekniği denemeniz gerektiğini düşünüyorum, istediğiniz kadar sayfayı çağırmak, tüm sayfaların eşzamansız olarak her bir sayfa yanıtını beklemeden bağımsız olarak aynı anda çalışmasına yardımcı olacaktır.

form_action_page.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.

  //your form db insertion or other code goes here do what ever you want //above code will work as background job this line will direct hit before //above lines response     
                ?>
                <?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
//here do your background operations it will not halt main page
    ?>

Not: url parametrelerini döngü olarak göndermek istiyorsanız şu cevabı izleyin: https://stackoverflow.com/a/41225209/6295712


0

Benim durumumda 3 parametrem var, bunlardan biri string (mensaje):

exec("C:\wamp\bin\php\php5.5.12\php.exe C:/test/N/trunk/api/v1/Process.php $idTest2 $idTest3 \"$mensaje\" >> c:/log.log &");

Process.php dosyamda bu koda sahibim:

if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];

0

Bu benim için çalışıyor. Tyr this

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);

değerlerim varsa ne olur? bir formdan ve ajax ve serialize()function yoluyla gönderiyorum . örneğin, index.php form ile sahibim ve ajax üzerinden index2.php'ye değer gönderiyorum. index2.php'de arka planda veri gönderimi yapmak istiyorum ne önerirsiniz? teşekkürler
khalid JA

0

İşleri paralel ve eşzamansız olarak yürütmek için Amphp kullanın .

Kitaplığı kurun

composer require amphp/parallel-functions

Kod örneği

<?php

require "vendor/autoload.php";

use Amp\Promise;
use Amp\ParallelFunctions;


echo 'started</br>';

$promises[1] = ParallelFunctions\parallel(function (){
    // Send Email
})();

$promises[2] = ParallelFunctions\parallel(function (){
    // Send SMS
})();


Promise\wait(Promise\all($promises));

echo 'finished';

Kullanım durumunuz için aşağıdaki gibi bir şey yapabilirsiniz

<?php

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$responses = wait(parallelMap([
    'a@example.com',
    'b@example.com',
    'c@example.com',
], function ($to) {
    return send_mail($to);
}));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.