Come posso eseguire uno script PHP in background dopo aver inviato un modulo?


104

Problema
Ho un modulo che, una volta inviato, eseguirà il codice di base per elaborare le informazioni inviate e inserirlo in un database per la visualizzazione su un sito Web di notifica. Inoltre, ho un elenco di persone che si sono registrate per ricevere queste notifiche tramite e-mail e SMS. Questo elenco è banale come il momento (spingendo solo circa 150), tuttavia è sufficiente perché ci vuole più di un minuto per scorrere l'intera tabella degli iscritti e inviare oltre 150 e-mail. (Le e-mail vengono inviate individualmente come richiesto dagli amministratori di sistema del nostro server di posta elettronica a causa delle politiche di posta elettronica di massa.)

Durante questo periodo, l'individuo che ha pubblicato l'avviso rimarrà seduto sull'ultima pagina del modulo per quasi un minuto senza alcun rinforzo positivo che la sua notifica sia stata pubblicata. Questo porta ad altri potenziali problemi, tutti che hanno soluzioni possibili che ritengo tutt'altro che ideali.

  1. Innanzitutto, l'autore potrebbe pensare che il server sia in ritardo e fare di nuovo clic sul pulsante "Invia", facendo ricominciare o eseguire due volte lo script. Potrei risolvere questo problema utilizzando JavaScript per disabilitare il pulsante e sostituire il testo per dire qualcosa come "Elaborazione ...", tuttavia questo è tutt'altro che ideale perché l'utente sarà ancora bloccato sulla pagina per la durata dell'esecuzione dello script. (Inoltre, se JavaScript è disabilitato, il problema persiste.)

  2. In secondo luogo, il poster potrebbe chiudere prematuramente la scheda o il browser dopo aver inviato il modulo. Lo script continuerà a funzionare sul server fino a quando non proverà a riscrivere nel browser, tuttavia se l'utente accede a qualsiasi pagina all'interno del nostro dominio (mentre lo script è ancora in esecuzione), il browser si blocca caricando la pagina fino al termine dello script . (Questo accade solo quando una scheda o una finestra del browser è chiusa e non l'intera applicazione del browser.) Tuttavia, questo è tutt'altro che ideale.

(Possibile) Soluzione
Ho deciso di suddividere la parte "e-mail" dello script in un file separato che posso chiamare dopo che la notifica è stata pubblicata. Inizialmente pensavo di inserirlo nella pagina di conferma dopo che la notifica è stata pubblicata con successo. Tuttavia, l'utente non saprà che questo script è in esecuzione e qualsiasi anomalia non gli sarà evidente; Questo script non può fallire.

Ma cosa succede se posso eseguire questo script come processo in background? Quindi, la mia domanda è questa: come posso eseguire uno script PHP da attivare come servizio in background ed eseguire in modo completamente indipendente da ciò che l'utente ha fatto a livello di modulo?

EDIT: questo non può essere cronizzato. Deve essere eseguito nell'istante in cui viene inviato il modulo. Queste sono notifiche ad alta priorità. Inoltre, gli amministratori di sistema che eseguono i nostri server impediscono agli utenti di funzionare più frequentemente di 5 minuti.


Avvia un cron job e avvisa l'utente su un'altra pagina che la sua richiesta è in fase di elaborazione o qualcosa del genere.
Evan Mulawski

1
Vuoi eseguirlo regolarmente (ogni giorno o ogni ora) o su invio? Immagino che potresti usare php exec()ma non l'ho mai provato.
Simon

2
Uso semplicemente ajaxe inserisco sleep()con intervallo di tempo all'interno del ciclo (nel mio caso uso foreach) per eseguire il processo in background. Funziona perfettamente nel mio server. Non sono sicuro degli altri. Sto anche aggiungendo ignore_user_abort()e set_time_limit()(con l'ora di fine calcolata) prima del ciclo per assicurarmi che lo script non venga fermato dal server che utilizzo, anche secondo il mio test, lo script che completa l'attività senza ignore_user_abort()e set_time_limit().
Ari

Ho visto che hai già trovato una soluzione. Uso gearmancon php per gestire le attività in background. È perfetto da scalare se ti imbatti in tempi di elaborazione lunghi e carichi pesanti. Dovresti dare un'occhiata.
Andre Garcia

Seguire questa risposta su [StackOverflow] ( stackoverflow.com/a/46617107/6417678 )
Joshy Francis

Risposte:


89

Facendo qualche sperimentazione exece shell_execho scoperto una soluzione che ha funzionato perfettamente! Scelgo di utilizzare in shell_execmodo da poter registrare ogni processo di notifica che avviene (o meno). ( shell_execritorna come una stringa e questo è stato più facile che usare exec, assegnando l'output a una variabile e quindi aprendo un file in cui scrivere.)

Sto usando la seguente riga per richiamare lo script di posta elettronica:

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

È importante notare il &alla fine del comando (come sottolineato da @netcoder). Questo comando UNIX esegue un processo in background.

Le variabili extra racchiuse tra virgolette singole dopo il percorso dello script sono impostate come $_SERVER['argv']variabili che posso chiamare all'interno del mio script.

Lo script di posta elettronica viene quindi inviato al mio file di registro utilizzando >>e produrrà qualcosa del genere:

[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)

Grazie. Questo mi ha salvato la vita.
Liam Bailey

Cos'è quello $post_iddopo il percorso dello script php?
Perocat

@ Perocat è una variabile che stai inviando allo script. In questo caso sta inviando un ID che sarà un parametro per lo script che viene chiamato.
Chique

Dubito che questo funziona perfettamente, vedo stackoverflow.com/questions/2212635/...
symcbean

1
C'è un suggerimento di modifica in sospeso per uscire $post_id; Avrei preferito lanciare direttamente a un numero: (int) $post_id. (Richiamando la tua attenzione per decidere quale è meglio.)
Al.G.

19

Su server Linux / Unix, è possibile eseguire un lavoro in background utilizzando proc_open :

$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);

L' &essere la parte importante qui. Lo script continuerà anche se lo script originale è terminato.


Funziona quando non chiamo proc_closequando l'attività è stata completata. proc_closefa sì che lo script si blocchi da 15 a 25 secondi. Avrei bisogno di mantenere un registro utilizzando le informazioni sulla pipe, quindi devo aprire un file su cui scrivere, chiuderlo e quindi chiudere la connessione proc. Quindi, sfortunatamente, questa soluzione non funziona per me.
Michael Irigoyen

3
Certo che non chiami proc_close, questo è il punto! E sì, puoi reindirizzare a un file con proc_open. Vedi risposta aggiornata.
netcoder

@netcoder: cosa succede se non voglio memorizzare il risultato in alcun file. quali modifiche sono richieste in stdout?
naggarwal11

9

Di tutte le risposte, nessuna ha considerato la ridicolmente facile funzione fastcgi_finish_request , che quando chiamata, scarica tutto l'output rimanente nel browser e chiude la sessione Fastcgi e la connessione HTTP, lasciando che lo script venga eseguito in background.

Un esempio:

<?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,

questo è esattamente quello che stavo cercando, in shell_exec devo in qualche modo trasferire i dati inviati allo script eseguito, il che nel mio caso è un compito che richiede tempo e rende le cose molto complicate.
Aurangzeb

7

PHP exec("php script.php")può farlo.

Dal manuale :

Se un programma viene avviato con questa funzione, affinché possa continuare a funzionare in background, l'output del programma deve essere reindirizzato a un file oa un altro flusso di output. In caso contrario, PHP si bloccherà fino al termine dell'esecuzione del programma.

Quindi, se reindirizzi l'output a un file di registro (che è comunque una buona idea), lo script chiamante non si bloccherà e lo script email verrà eseguito in bg.


1
Grazie, ma non ha funzionato. Lo script "si blocca" ancora mentre elabora il secondo file.
Michael Irigoyen

Guardate l'affido php.net/manual/en/function.exec.php#80582 sembra che questo è perché safe_mode è abilitato. C'è una soluzione alternativa in Unix.
Simon

Sfortunatamente, la modalità provvisoria non è abilitata sulla nostra installazione. È strano che sia ancora sospeso.
Michael Irigoyen

6

E perché non fare una richiesta HTTP sullo script e ignorare la risposta?

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

Se effettui la tua richiesta sullo script devi chiamare il tuo server web, lo eseguirà in background e puoi (nel tuo script principale) mostrare un messaggio che dice all'utente che lo script è in esecuzione.


4

Cosa ne pensi di questo?

  1. Lo script PHP che contiene il modulo salva un flag o un valore in un database o file.
  2. Un secondo script PHP esegue periodicamente il polling di questo valore e, se è stato impostato, attiva lo script di posta elettronica in modo sincrono.

Questo secondo script PHP dovrebbe essere impostato per essere eseguito come cron.


Grazie, ma ho aggiornato la domanda originale. I cron non sono un'opzione in questo caso.
Michael Irigoyen

4

Come so che non puoi farlo in modo semplice (vedi fork exec ecc (non funziona sotto Windows)), potresti invertire l'approccio, usa lo sfondo del browser postando il modulo in ajax, quindi se il post continua lavoro non hai tempo di attesa.
Questo può aiutare anche se devi fare una lunga elaborazione.

Per l'invio di posta è sempre consigliabile utilizzare uno spooler, può essere un server smtp locale e veloce che accetta le tue richieste e le spool sull'MTA reale o mette tutto in un DB, piuttosto che usare un cron che spool la coda.
Il cron potrebbe essere su un'altra macchina che chiama lo spooler come URL esterno:

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

3

Il cron job in background sembra una buona idea per questo.

Avrai bisogno dell'accesso ssh alla macchina per eseguire lo script come cron.

$ php scriptname.php per eseguirlo.


1
non c'è bisogno di ssh. Molti host non consentono ssh, ma hanno il supporto per cron.
Teson

Grazie, ma ho aggiornato la domanda originale. I cron non sono un'opzione in questo caso.
Michael Irigoyen

3

Se puoi accedere al server tramite ssh ed eseguire i tuoi script puoi creare un semplice server fifo usando php (anche se dovrai ricompilare php con il posixsupporto per fork).

Il server può essere scritto in qualsiasi cosa, probabilmente puoi farlo facilmente in Python.

Oppure la soluzione più semplice sarebbe inviare una HttpRequest e non leggere i dati di ritorno, ma il server potrebbe distruggere lo script prima che termini l'elaborazione.

Server di esempio:

<?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);
?>

Cliente di esempio:

<?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

Il modo più semplice per eseguire uno script PHP in background è

php script.php >/dev/null &

Lo script verrà eseguito in background e la pagina raggiungerà anche la pagina delle azioni più velocemente.


2

Supponendo che tu stia eseguendo su una piattaforma * nix, usa crone l' phpeseguibile.

MODIFICARE:

Ci sono già un certo numero di domande che chiedono di "eseguire php senza cron" su SO. Eccone uno:

Pianifica gli script senza utilizzare CRON

Detto questo, la risposta exec () sopra sembra molto promettente :)


Grazie, ma ho aggiornato la domanda originale. I cron non sono un'opzione in questo caso.
Michael Irigoyen

2

Se sei su Windows, cerca proc_openo popen...

Ma se siamo sullo stesso server "Linux" che esegue cpanel, questo è l'approccio giusto:

#!/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
}
?>

Non usarli fork()o curlse dubiti di poterli gestire, è come abusare del tuo server

Infine, sul script.phpfile che viene chiamato sopra, prendi nota di questo assicurati di aver scritto:

<?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

?>

Ci scusiamo per averlo modificato successivamente perché sto usando il mio telefono cellulare e in effetti è molto più difficile che scrivere uno script exec (). Lol;)
ArbZ

2

per chi lavora in background penso che dovresti provare questa tecnica che ti aiuterà a chiamare tutte le pagine che ti piacciono, tutte le pagine verranno eseguite contemporaneamente in modo indipendente senza attendere la risposta di ogni pagina come asincrona.

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
    ?>

PS: se vuoi inviare i parametri dell'URL come loop, segui questa risposta: https://stackoverflow.com/a/41225209/6295712


0

Nel mio caso ho 3 parametri, uno di questi è 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 &");

Nel mio Process.php ho questo codice:

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

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

0

Questo funziona per me. Tyr questo

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

cosa succede se ho valori di post? da un modulo e lo invio tramite ajax e serialize()funzione. ad esempio, ho index.php con form e invio valore tramite ajax a index2.php voglio eseguire l'invio di dati in index2.php in background cosa mi consigliate? grazie
khalid JA

0

Usa Amphp per eseguire lavori in parallelo e in modo asincrono.

Installa la libreria

composer require amphp/parallel-functions

Esempio di codice

<?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';

Per il tuo caso d'uso, puoi fare qualcosa come di seguito

<?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);
}));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.