La meilleure façon de gérer un script php de longue durée?

j'ai un script PHP qui prend beaucoup de temps (5-30 minutes) à compléter. Juste au cas où cela importe, le script utilise curl pour racler les données d'un autre serveur. C'est la raison pour laquelle cela prend autant de temps; il faut attendre que chaque page se charge avant de la traiter et de passer à la suivante.

je veux être capable d'initier le script et le laisser faire jusqu'à ce qu'il soit fait, ce qui va mettre un drapeau dans une table de base de données.

Ce que j'ai besoin de savoir, c'est comment être possibilité de terminer la requête http avant que le script ne soit terminé. Aussi, est un script php la meilleure façon de le faire?

66
demandé sur kbanman 2010-02-06 12:16:59

14 réponses

certainement cela peut être fait avec PHP, mais vous ne devez pas le faire comme une tâche de fond - le nouveau processus doit être dissocié du groupe de processus où il est initié.

puisque les gens continuent à donner la même mauvaise réponse à cette FAQ, j'ai écrit une réponse plus complète ici:

http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html

des commentaires:

la version courte est shell_exec('echo /usr/bin/php -q longThing.php | at now'); mais les raisons pour lesquelles sont un peu long pour l'inclusion ici.

97
répondu symcbean 2017-04-20 08:38:43

la manière rapide et sale serait d'utiliser la fonction ignore_user_abort en php. Ceci dit en gros: ne vous souciez pas de ce que l'utilisateur fait, exécutez ce script jusqu'à ce qu'il soit terminé. C'est quelque peu dangereux si c'est un site faisant face au public (parce qu'il est possible, que vous finissiez par avoir 20 versions++ du script tournant en même temps si il est initié 20 fois).

la voie" propre " (au moins IMHO) est de mettre un drapeau (dans le db par exemple) quand vous voulez initier le traitez et lancez une cronjob toutes les heures (ou plus) pour vérifier si ce drapeau est activé. Si elle est définie, le script à long terme commence, si elle n'est pas définie, rien n'arrive.

11
répondu FlorianH 2010-02-06 09:26:53

vous pouvez utiliser exec ou système pour commencer un travail de fond, et puis faire le travail dans ce.

aussi, il y a de meilleures approches pour gratter la toile que celle que vous utilisez. Vous pouvez utiliser une approche filetée (plusieurs threads faisant une page à la fois), ou une en utilisant un eventiloop (un thread faisant plusieurs pages à la fois). Mon approche personnelle en utilisant Perl serait d'utiliser AnyEvent:: HTTP .

ETA: symcbean expliquait comment détacher correctement le processus de fond here .

8
répondu Leon Timmermans 2017-05-23 11:55:00

Non, PHP n'est pas la meilleure solution.

Je ne suis pas sûr pour Ruby ou Perl, mais avec Python vous pourriez réécrire votre scraper de page pour être multi-threadé et il fonctionnerait probablement au moins 20 fois plus vite. Écrire des applications multi-threadées peut être un peu difficile, mais la toute première application Python que j'ai écrite était un grattoir de page multi-threadé. Et vous pouvez simplement appeler le script Python depuis votre page PHP en utilisant l'une des fonctions d'exécution shell.

5
répondu jamieb 2011-12-21 17:26:08

PHP peut ou peut ne pas être le meilleur outil, mais vous savez comment l'utiliser, et le reste de votre application est écrite à l'aide. Ces deux qualités, combinées avec le fait que PHP est "assez bon" font un argument assez solide pour l'utiliser, au lieu de Perl, Ruby, ou Python.

si votre but est d'apprendre une autre langue, choisissez-en une et utilisez-la. La langue que vous avez mentionnée fera l'affaire, pas de problème. J'aime Perl, mais ce que tu aimes peut être différent.

Symcbean a quelques bons conseils sur la façon de gérer les processus de fond à son lien.

en bref, écrire un script PHP CLI pour gérer les longs bits. Assurez-vous que l'état des rapports d'une certaine façon. Créez une page php pour gérer les mises à jour d'état, en utilisant AJAX ou les méthodes traditionnelles. Votre script de démarrage démarrera le processus en cours d'exécution dans sa propre session, et vous confirmera que le processus est en cours.

bonne chance.

3
répondu daotoad 2010-02-08 06:28:55

Oui, vous pouvez le faire en PHP. Mais en plus de PHP, il serait sage d'utiliser un Gestionnaire de File d'attente. Voici la stratégie:

  1. divisez votre grande tâche en tâches plus petites. Dans votre cas, chaque tâche pourrait charger une seule page.

  2. envoyer chaque petite tâche à la file d'attente.

  3. faites la queue quelque part.

L'utilisation de cette stratégie présente les avantages suivants:

  1. pour les tâches de longue durée, il a la capacité de récupérer dans le cas où un problème mortel se produit au milieu de la course -- pas besoin de commencer dès le début.

  2. si vos tâches ne doivent pas être exécutées de façon séquentielle, vous pouvez exécuter plusieurs travailleurs pour exécuter des tâches simultanément.

Vous avez une variété de options (ce n'est que quelques-unes):

  1. RabbitMQ ( https://www.rabbitmq.com/tutorials/tutorial-one-php.html )
  2. ZeroMQ ( http://zeromq.org/bindings:php )
  3. si vous utilisez le cadre Laravel, les files d'attente sont intégrées ( https://laravel.com/docs/5.4/queues ), avec pilotes pour AWS SES, Redis, Beanstalkd
3
répondu aljo f 2017-05-23 05:06:59

je suis d'accord avec les réponses qui disent que ce doit être exécuté dans un processus d'arrière-plan. Mais il est également important que vous signaliez le statut afin que l'utilisateur sache que le travail est en cours.

lorsque vous recevez la requête PHP pour lancer le processus, vous pouvez stocker dans une base de données une représentation de la tâche avec un identifiant unique. Ensuite, démarrer le grattage d'écran processus, en lui passant l'identifiant unique. Rapport à l'application iPhone qui la tâche a été commencé et qu'il devrait vérifier une URL spécifiée, contenant le nouvel identifiant de tâche, pour obtenir le statut le plus récent. L'application iPhone peut maintenant Poller (ou même" long poll") cette URL. Entre-temps, le processus de fond mettrait à jour la représentation de la tâche dans la base de données, car il fonctionnait avec un pourcentage d'achèvement, l'étape actuelle, ou tout autre indicateur de l'état que vous souhaitez. Et une fois terminé, il placerait un drapeau terminé.

1
répondu Jacob 2010-02-06 19:58:59

vous pouvez l'envoyer sous forme de requête XHR (Ajax). Les Clients n'ont généralement pas de timeout pour XHRs, contrairement aux requêtes HTTP normales.

1
répondu JAL 2010-02-06 23:51:28

je me rends compte que c'est une question assez ancienne mais je voudrais lui donner une chance. Ce script essaie de répondre à la fois à l'appel initial de coup d'envoi pour terminer rapidement et couper la charge lourde en plus petits morceaux. Je n'ai pas testé cette solution.

<?php
/**
 * crawler.php located at http://mysite.com/crawler.php
 */

// Make sure this script will keep on runing after we close the connection with
// it.
ignore_user_abort(TRUE);


function get_remote_sources_to_crawl() {
  // Do a database or a log file query here.

  $query_result = array (
    1 => 'http://exemple.com',
    2 => 'http://exemple1.com',
    3 => 'http://exemple2.com',
    4 => 'http://exemple3.com',
    // ... and so on.
  );

  // Returns the first one on the list.
  foreach ($query_result as $id => $url) {
    return $url;
  }
  return FALSE;
}

function update_remote_sources_to_crawl($id) {
  // Update my database or log file list so the $id record wont show up
  // on my next call to get_remote_sources_to_crawl()
}

$crawling_source = get_remote_sources_to_crawl();

if ($crawling_source) {


  // Run your scraping code on $crawling_source here.


  if ($your_scraping_has_finished) {
    // Update you database or log file.
    update_remote_sources_to_crawl($id);

    $ctx = stream_context_create(array(
      'http' => array(
        // I am not quite sure but I reckon the timeout set here actually
        // starts rolling after the connection to the remote server is made
        // limiting only how long the downloading of the remote content should take.
        // So as we are only interested to trigger this script again, 5 seconds 
        // should be plenty of time.
        'timeout' => 5,
      )
    ));

    // Open a new connection to this script and close it after 5 seconds in.
    file_get_contents('http://' . $_SERVER['HTTP_HOST'] . '/crawler.php', FALSE, $ctx);

    print 'The cronjob kick off has been initiated.';
  }
}
else {
  print 'Yay! The whole thing is done.';
}
1
répondu Francisco Luz 2013-06-27 09:02:16

je voudrais proposer une solution qui est un peu différente de symcbean, principalement parce que j'ai une exigence supplémentaire que le processus de longue durée doivent être exécutés comme un autre utilisateur, et non comme apache / www-data user.

première solution utilisant cron pour sonder une table des tâches de fond:

  • la page Web de PHP insère dans une table des tâches de fond, l'état 'SUBMITTED'
  • cron fonctionne une fois toutes les 3 minutes, en utilisant un autre utilisateur, exécutant le script PHP CLI qui vérifie la table des tâches d'arrière-plan pour les lignes 'SUBMITTED'
  • PHP CLI mettra à jour la colonne d'état dans la ligne dans 'PROCESSING' et commencera le traitement, après l'achèvement il sera mis à jour à 'COMPLETED'

Deuxième solution à l'aide de Linux inotify installation:

  • la page Web de PHP met à jour un fichier de contrôle avec les paramètres définis par l'utilisateur, et donne également un ID de tâche
  • script shell (en tant qu'utilisateur non-www) exécutant inotifywait attendra que le fichier de contrôle soit écrit
  • après que le fichier de contrôle est écrit, un événement close_write sera soulevé et le script shell continuera
  • le script shell exécute PHP CLI pour faire le long processus
  • PHP CLI écrit la sortie dans un fichier journal identifié par l'id de tâche, ou met à jour la progression dans une table d'état
  • PHP web page pourrait consulter le fichier journal (basé sur l'id de tâche) pour montrer l'état d'avancement du processus long en cours, ou il pourrait également interroger la table d'état

des informations supplémentaires peuvent être trouvées dans mon article: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html

1
répondu YudhiWidyatama 2016-01-31 12:32:25

j'ai fait des choses similaires avec Perl, double fork() et de détacher du processus parent. Tout le travail de récupération http doit être fait dans le processus Fourché.

0
répondu Alexandr Ciornii 2010-02-06 19:41:36

utilisez un mandataire pour déléguer la demande.

0
répondu zerodin 2010-10-29 22:17:03

ce que j'utilise toujours est une de ces variantes (parce que différentes saveurs de Linux ont des règles différentes sur la gestion de la sortie/certains programmes sortie différemment):

Variante I @exec('./ myscript.php \1> / dev / null \2> / dev / null &');

variante II @exec ('php-F myscript.php \1> / dev / null \2> / dev / null &');

variante III @exec ('nohup myscript.php \1> / dev / null \2> / dev / null &');

vous pourriez avoir à installer"nohup". Mais par exemple, lorsque j'automatisais les conversations vidéo FFMPEG, l'interface de sortie n'était pas gérée à 100% par la redirection des flux de sortie 1 et 2, donc j'ai utilisé nohup et redirigé la sortie.

0
répondu dr burns 2011-09-07 10:42:47

si vous avez un long script, alors divisez le travail de page à l'aide du paramètre input pour chaque tâche.(puis chaque page agit comme un fil) I. e si la page a 1 lac product_keywords long process loop alors au lieu de loop faites la logique pour un mot-clé et passez ce mot-clé de magic ou cornjobpage.php (dans l'exemple suivant)

et pour le travailleur d'arrière-plan je pense que vous devriez essayer cette technique il aidera à appeler autant de pages que vous comme toutes les pages seront exécutées à la fois indépendamment sans attendre la réponse asynchrone de chaque page.

cornjobpage.php //en de la page d'accueil

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

page test.php

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

PS:Si vous voulez envoyer des paramètres d'url en boucle, alors suivez cette réponse: https://stackoverflow.com/a/41225209/6295712

0
répondu Hassan Saeed 2017-05-23 11:55:00