Question

Je travaille sur une application web un peu grand, et le back-end est la plupart du temps en PHP. Il y a plusieurs endroits dans le code où je dois terminer une tâche, mais je ne veux pas faire l'utilisateur attendre le résultat. Je dois leur envoyer un message de bienvenue Par exemple, lors de la création d'un nouveau compte,. Mais quand ils ont frappé le bouton « Terminer l'enregistrement », je ne veux pas les faire attendre jusqu'à ce que l'e-mail soit envoyé, je veux juste commencer le processus, et renvoyer un message à l'utilisateur tout de suite.

Jusqu'à présent, dans certains endroits, je me sers de ce qui ressemble à un hack avec exec (). En fait faire des choses comme:

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

Ce qui semble fonctionner, mais je me demande s'il y a une meilleure façon. Je songe à la rédaction d'un système qui met en attente des tâches dans une table MySQL, et un script PHP long en cours d'exécution distinct qui interroge cette table une fois par seconde, et exécute toutes les nouvelles tâches qu'il trouve. Cela aurait aussi l'avantage de me laisser diviser les tâches entre plusieurs machines de travail à l'avenir si je devais.

Suis-je Réinventer la roue? Y at-il une meilleure solution que l'exec () ou pirater la file d'attente MySQL?

Était-ce utile?

La solution

Je l'ai utilisé l'approche de mise en attente, et il fonctionne bien que vous pouvez reporter que le traitement jusqu'à ce que la charge du serveur est inactif, vous permettant de gérer votre charge de façon très efficace si vous pouvez cloisonner « tâches qui ne sont pas urgentes » facilement.

vos propres modèles est pas trop difficile, voici quelques autres options pour vérifier:

  • gearman - cette réponse a été écrit en 2009, et depuis lors gearman semble une option populaire, voir commentaires ci-dessous <. / li>
  • ActiveMQ si vous voulez une file d'attente de messages open source complète soufflé.
  • ZeroMQ - c'est une bibliothèque assez cool socket qui le rend facile d'écrire du code distribué sans avoir à se soucier trop sur la programmation de prise lui-même. Vous pouvez l'utiliser pour faire la queue un message sur un seul hôte - vous simplement avoir votre webapp pousser quelque chose à une file d'attente d'une application console fonctionnant en continu consommerait à la prochaine occasion appropriée
  • beanstalkd - ne se trouve que celui-ci tout en écrivant cette réponse, mais il semble intéressant
  • dropr est un projet de file d'attente de messages sur la base de PHP, mais n'a pas été activement maintenue depuis septembre 2010
  • php-enqueue est un récemment (2017) maintenu enveloppe autour d'une variété de file d'attente systèmes
  • Enfin, un blog sur l'utilisation memcached pour message Mise en attente

Une autre, peut-être plus simple, approche consiste à utiliser ignore_user_abort - une fois que vous avez envoyé la page à l'utilisateur, vous pouvez faire votre traitement final sans crainte de résiliation prématurée, bien que cela n'avoir pour effet de prolonger l'air de la charge de la page du point de vue de l'utilisateur.

Autres conseils

Lorsque vous voulez juste d'exécuter une ou plusieurs requêtes HTTP sans avoir à attendre la réponse, il y a une solution simple de PHP, ainsi.

Dans le script d'appel:

$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

Sur le script.php appelé, vous pouvez appeler ces fonctions PHP dans les premières lignes:

ignore_user_abort(true);
set_time_limit(0);

Cela provoque le script de continuer à fonctionner sans limite de temps lorsque la connexion HTTP est fermé.

Une autre façon de processus fourche est par boucle. Vous pouvez configurer vos tâches internes comme webservice. Par exemple:

Ensuite, dans vos scripts accès utilisateur à faire des appels au service:

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

Votre service peut garder une trace de la file d'attente des tâches avec MySQL ou tout ce que vous aimez le point est le suivant: il est empaqueté dans le service et votre script est tout simplement les URL consommaient. Cela vous libère pour déplacer le service vers une autre machine / serveur si nécessaire (facilement extensible).

Ajout d'autorisation http ou d'un régime d'autorisation personnalisé (comme les services Web d'Amazon) vous permet d'ouvrir vos tâches à être consommés par d'autres personnes / services (si vous voulez) et vous pouvez le prendre plus loin et ajouter un service de surveillance au-dessus de garder une trace de l'état de la file d'attente et des tâches.

Il prend un peu de travail de mise en place, mais il y a beaucoup d'avantages.

Je l'ai utilisé beanstalkd pour un projet, et prévu de nouveau. Je l'ai trouvé pour être un excellent moyen d'exécuter des processus asynchrones.

Un couple de choses que je l'ai fait avec elle sont:

  • Redimensionnement de l'image - et avec un script PHP file d'attente légèrement chargé faisant passer à une base CLI, le redimensionnement des images volumineuses (2 Mo +) est très bien passé, mais en essayant de redimensionner les mêmes images dans une instance mod_php était en cours d'exécution régulièrement dans l'espace mémoire questions (je limité le processus PHP 32Mo, et le redimensionnement ont plus que cela)
  • proche des contrôles ultérieurs - beanstalkd a des retards dont il dispose (faire ce travail disponible pour exécuter seulement après X secondes) - je peux tirer de 5 ou 10 chèques pour un événement, un peu plus tard dans le temps

J'ai écrit un système basé sur Zend Framework pour décoder une url « nice », donc par exemple, de redimensionner une image, il appellerait QueueTask('/image/resize/filename/example.jpg'). L'URL a été décodé à un réseau (module, contrôleur, action, paramètres), puis converti en JSON pour injection à la queue elle-même.

Un long script cli en cours d'exécution, puis repris le travail de la file d'attente, il a couru (via Zend_Router_Simple), et le cas échéant, mettre l'information dans memcached pour le site Web PHP pour ramasser au besoin quand il a été fait.

Une ride j'ai aussi donné était que le cli-scriptum couru pour 50 seulement des boucles avant de redémarrer, mais si elle ne voulait redémarrer comme prévu, il le ferait immédiatement (en cours d'exécution par un bash-script). S'il y avait un problème et je l'ai fait exit(0) (la valeur par défaut pour ou exit; die();) il faudrait d'abord mettre en pause pendant quelques secondes.

Si juste une question de fournir des tâches coûteuses, en cas de php-FPM est pris en charge, pourquoi ne pas utiliser fastcgi_finish_request() fonction?

  

Cette fonction débusque toutes les données de réponse au client et se termine la demande. Cela permet à des tâches beaucoup de temps à effectuer sans quitter la connexion au client ouvert.

Vous n'utilisez pas vraiment asynchronisme de cette façon:

  1. tout votre code principal en premier.
  2. Exécuter <=>.
  3. tous les trucs lourds.

Encore une fois php-FPM est nécessaire.

Voici une classe simple je Codé pour mon application Web. Il permet de bifurquer des scripts PHP et d'autres scripts. Fonctionne sous UNIX et 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);
        }
    }
}

Ceci est la même méthode que je l'ai utilisé pendant quelques années et je n'ai pas vu ou rien trouvé de mieux. Comme l'a dit, PHP est un seul thread, donc il n'y a pas grand-chose que vous pouvez faire.

Je l'ai fait ajouté un niveau supplémentaire pour ceci et cela devient et stocker l'ID de processus. Cela me permet de rediriger vers une autre page et que l'utilisateur assis sur cette page, en utilisant AJAX pour vérifier si le processus est terminé (id processus n'existe plus). Ceci est utile pour les cas où la longueur du script causerait le navigateur à délai d'attente, mais l'utilisateur doit attendre pour ce script pour terminer avant l'étape suivante. (Dans mon cas, il a été le traitement de gros fichiers ZIP avec CSV comme des fichiers qui ajoutent jusqu'à 30 000 enregistrements à la base de données après que l'utilisateur doit confirmer certaines informations.)

J'ai aussi utilisé un processus similaire pour la génération de rapports. Je ne suis pas sûr que j'utiliser « traitement de fond » pour quelque chose comme un e-mail, à moins qu'il ya un vrai problème avec un SMTP lent. Au lieu de cela, je pourrais utiliser une table comme une file d'attente, puis un processus qui exécute chaque minute pour envoyer les e-mails au sein de la file d'attente. Vous devez être warry d'envoyer des e-mails deux fois ou d'autres problèmes similaires. Je considérerais un processus de mise en attente similaire pour d'autres tâches aussi bien.

PHP multithreading, juste pas activé par défaut, il y a une extension appelée pthreads qui fait exactement cela. Vous aurez besoin de PHP compilé avec ZTS bien. (Thread-safe) Liens:

Exemples

Autre tutoriel

pthreads PECL Extension

Il est une excellente idée d'utiliser cURL comme suggéré par rojoca.

Voici un exemple. Vous pouvez surveiller text.txt tandis que le script est en cours d'exécution en arrière-plan:

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

?>

Malheureusement, PHP n'a aucune sorte de capacités de filetage natif. Donc, je pense que dans ce cas, vous avez pas d'autre choix que d'utiliser une sorte de code personnalisé pour faire ce que vous voulez faire.

Si vous effectuez une recherche sur le net pour des trucs threading PHP, certaines personnes ont trouvé des moyens pour simuler les discussions sur PHP.

Si vous définissez l'en-tête HTTP Content-Length dans votre réponse « Merci pour votre inscription », le navigateur doit fermer la connexion après le nombre d'octets sont reçus. Cela laisse le processus côté serveur en cours d'exécution (en supposant que ignore_user_abort est réglé) pour qu'il puisse terminer de travailler sans l'utilisateur final attente.

Bien sûr, vous aurez besoin de calculer la taille de votre contenu de la réponse avant de rendre les en-têtes, mais c'est assez facile pour de courtes réponses (sortie d'écriture à une chaîne, appelez strlen (), en-tête d'appel (), rendu chaîne).

Cette approche a l'avantage de pas vous obligeant à gérer une file d'attente « frontal », et bien que vous pourriez avoir besoin de faire un travail sur l'arrière afin d'éviter des processus enfants HTTP course de marcher sur l'autre, qui est quelque chose que vous aviez besoin de faire déjà, de toute façon.

Si vous ne voulez pas les ActiveMQ Full Blown, je vous recommande de considérer RabbitMQ . RabbitMQ est léger messagerie qui utilise le AMQP norme .

Je recommande de regarder aussi dans php-amqplib - une bibliothèque client AMQP populaire pour accéder AMQP courtiers de message en fonction.

Je pense que vous devriez essayer cette technique, il vous aidera à appeler autant que les pages que vous aimez toutes les pages fonctionneront à la fois de manière indépendante, sans attendre chaque réponse de page asynchrone.

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: si vous voulez envoyer des paramètres d'URL en boucle puis suivre cette réponse: https://stackoverflow.com/a/41225209 / 6295712

Frai de nouveaux processus sur le serveur en utilisant ou directement sur exec() un autre serveur en utilisant l'échelle ne boucle pas si bien du tout, si nous optons pour exec vous remplissez essentiellement votre serveur avec de longs processus de fonctionnement qui peuvent être pris en charge par d'autres serveurs non face à Internet, et en utilisant les liens pelotonner un autre serveur, sauf si vous construisez dans une sorte d'équilibrage de charge.

Je l'ai utilisé Gearman dans quelques situations et je trouve mieux pour ce genre de cas d'utilisation. Je peux utiliser un seul serveur de file d'attente pour gérer essentiellement faire la queue de tous les emplois devant être fait par le serveur et faites tourner les serveurs de travailleurs, dont chacun peut exécuter autant d'instances du processus de travail selon les besoins, et élargir le nombre de serveurs de travail selon les besoins et les tourner lorsqu'ils ne sont pas nécessaires. Il est également laissé me faire taire les processus de travail tout à fait en cas de besoin et les files d'attente des travaux jusqu'à ce que les travailleurs reviennent en ligne.

PHP est un langage mono-thread, donc il n'y a aucun moyen officiel pour démarrer un processus asynchrone avec elle autre que l'utilisation ou exec popen. Il y a un blog sur cette ici . Votre idée d'une file d'attente dans MySQL est une bonne idée.

Votre exigence spécifique est ici pour envoyer un e-mail à l'utilisateur. Je suis curieux de savoir pourquoi vous essayez de le faire de manière asynchrone depuis l'envoi d'un e-mail est une tâche assez trivial et rapide à réaliser. Je suppose que si vous envoyez des tonnes de courrier électronique et votre fournisseur d'accès vous bloque sur des soupçons de spamming, qui pourrait être une raison de faire la queue, mais à part ça, je ne peux penser à aucune raison de le faire de cette façon.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top