Frage

Ich arbeite an einer etwas größeren Web-Anwendung, und das Backend ist meist in PHP. Es gibt mehrere Stellen im Code, wo ich brauche Aufgabe abzuschließen, aber ich will nicht den Benutzer wartet auf das Ergebnis machen. Wenn zum Beispiel ein neues Konto erstellen, muß ich ihnen eine willkommene E-Mail senden. Aber wenn sie drücken Sie die ‚Fertig stellen Registrierung‘ Knopf, ich will nicht, um sie warten, bis die E-Mail tatsächlich gesendet wird, ich will nur den Vorgang starten, und gibt eine Meldung an den Benutzer sofort.

Bisher mancherorts Ich habe mit was fühlt sich an wie ein Hack mit exec (). Im Grunde genommen, Dinge zu tun mögen:

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

Welche scheint zu funktionieren, aber ich frage mich, ob es ein besserer Weg. Ich betrachte ein System zu schreiben, die Aufgaben in einer MySQL-Tabelle Warteschlangen, und einen separaten PHP-Skript mit langer Laufzeit, die die Tabelle einmal pro Sekunde abfragt, und führe alle neue Aufgaben es findet. Dies hätte auch den Vorteil, dass ich die Aufgaben auf mehrere Arbeitscomputer in Zukunft teilen, wenn ich gebraucht.

Bin ich neu zu erfinden das Rad? Gibt es eine bessere Lösung als die exec () hacken oder die MySQL-Warteschlange?

War es hilfreich?

Lösung

Ich habe die Warteschlangen-Ansatz verwendet, und es funktioniert gut, wie Sie, dass die Verarbeitung, bis die Serverlast im Leerlauf verschieben können, lassen effektiv verwalten Sie Ihre Last recht, wenn Sie „Aufgaben, die nicht dringend sind“ abzuriegeln leicht.

Ihre eigene Rollen ist nicht zu schwierig, hier ein paar andere Optionen zu prüfen:

  • Gearman - wurde diese Antwort im Jahr 2009 geschrieben, und seitdem Gearman eine beliebte Option sieht, siehe Kommentare unten <. / li>
  • ActiveMQ , wenn Sie eine vollständige geblasen Open-Source-Nachrichtenwarteschlange wollen.
  • ZeroMQ - das ist eine ziemlich coole Socket-Bibliothek, die es einfach macht, verteilt Code zu schreiben, ohne sich Sorgen zu machen zu viel über die Socket-Programmierung selbst. Man kann es benutzen für die Nachricht auf einem einzigen Host Warteschlangen - Sie würden einfach Ihre Webapp Push etwas in eine Warteschlange, die eine kontinuierlich laufende Konsolenanwendung bei der nächsten passenden Gelegenheit verbrauchen würde
  • beanstalkd - nur dieses eine gefunden, während diese Antwort zu schreiben, sieht aber interessant
  • dropr ist ein PHP-basiertes Message-Queue-Projekt, hat aber nicht aktiv gepflegt seit September 2010
  • php-enqueue ist eine vor kurzem (2017) gehalten Hülle um eine Vielzahl von Warteschlangen Systeme
  • Schließlich ist ein Blog-Post über Memcached für Message Queuing

Ein weiterer, vielleicht einfacher Ansatz ist die Verwendung ignore_user_abort - sobald Sie die Seite an den Benutzer gesendet haben, Sie können Ihre Endverarbeitung ohne Angst vor vorzeitiger Beendigung tun, obwohl dies den Effekt erscheint die Seite Last von der Benutzerperspektive zu verlängern hat.

Andere Tipps

Wenn Sie nur ein oder mehrere HTTP-Anfragen ausführen möchten, ohne auf die Antwort zu warten, gibt es eine einfache PHP-Lösung, wie gut.

In der anrufenden Skript:

$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

Auf dem gerufenen script.php, können Sie diese PHP-Funktionen in den ersten Zeilen aufrufen:

ignore_user_abort(true);
set_time_limit(0);

Dies bewirkt, dass das Skript ohne zeitliche Begrenzung weiterlaufen, wenn die HTTP-Verbindung geschlossen wird.

Eine weitere Möglichkeit, Prozesse gabeln ist über curl. Sie können Ihre internen Aufgaben als Webservice einrichten. Zum Beispiel:

Dann in Ihrem Benutzer zugegriffen Skripte Anrufe tätigen zu dem Service:

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

Ihr Dienst kann mit mysql Spur der Warteschlange von Aufgaben halten oder was auch immer Sie mögen der Punkt ist: es ist alles in dem Service und das Skript eingewickelt ist nur URLs raubend. Das befreit Sie bis zu dem Dienst auf einem anderen Rechner / Server bei Bedarf (dh leicht skalierbar) zu bewegen.

http Genehmigung oder eine benutzerdefinierte Genehmigungsregelung Hinzufügen (wie Amazon Web Services) können Sie öffnen Sie Ihre Aufgaben, die von anderen Personen / Dienstleistungen verbraucht werden (wenn Sie möchten), und man kann es weiter gehen und einen Monitoring-Service oben in der verfolgt Warteschlange und Aufgabenstatus.

Es dauert ein wenig Setup-Arbeit, aber es gibt eine Menge Vorteile.

Ich habe verwendet Beanstalkd für ein Projekt, und geplant wieder. Ich habe es gefunden eine hervorragende Möglichkeit, um asynchrone Prozesse auszuführen.

Ein paar Dinge, die ich mit ihm fertig sind:

  • Bild Redimensionierung - und mit einer leicht belasteten Warteschlange auf einen CLI-basierten PHP-Skript passing off, groß (2 MB +) Größe der Bilder ganz gut gearbeitet, aber versuchen, die gleichen Bilder in einer Mod_php Instanz, um die Größe regelmäßig in der Speicher-Raum ausgeführt wurde, Ausgaben (I beschränkt auf 32MB den PHP-Prozess und die Redimensionierung mehr nahm als die)
  • in naher Zukunft überprüft - beanstalkd hat Verzögerungen zur Verfügung, um es (machen diesen Job nur nach X Sekunden läuft) - so kann ich abfeuern 5 oder 10 prüft, ob ein Ereignis, ein wenig später in der Zeit

Ich schrieb ein Zend-Framework-basiertes System eine ‚nette‘ URL zu dekodieren, so beispielsweise ein Bild, um die Größe wäre es QueueTask('/image/resize/filename/example.jpg') nennen. Die URL wurde zuerst auf ein Array (Modul, Controller, Aktion, Parameter) decodiert und dann zur Injektion in die Warteschlange zu JSON umgewandelt selbst.

Ein langer Lauf cli Skript nahm dann den Auftrag aus der Warteschlange auf, lief es (über Zend_Router_Simple) und, falls erforderlich, stellte Informationen in PHP Memcached für die Website nach Bedarf zu holen, wenn es geschehen ist.

Eine Falte ich auch tat, war legen, dass die cli-Skript nur für 50 Schleifen lief vor dem Neustart, aber wenn es wollte, wie geplant neu zu starten, würde es dies sofort tun (über ein Bash-Skript ausgeführt wird). Wenn es ein Problem gab, und ich habe exit(0) (den Standardwert für exit; oder die();) wäre es zunächst für ein paar Sekunden Pause.

Wenn es nur eine Frage der Bereitstellung von teueren Aufgaben, bei php-fpm unterstützt wird, warum nicht verwenden fastcgi_finish_request() Funktion?

  

Diese Funktion leert alle Antwortdaten an den Client und beendet die Anfrage. Dies ermöglicht eine zeitaufwendige Aufgaben, ohne den Anschluss an den Client offen durchgeführt werden.

Sie haben nicht wirklich Asynchronität auf diese Weise verwenden:

  1. Machen Sie alle Ihre Haupt-Code zuerst.
  2. Ausführen fastcgi_finish_request().
  3. Machen Sie alle schweren Sachen.

Wieder einmal php-fpm benötigt wird.

Hier ist eine einfache Klasse, die ich für meine Web-Anwendung codiert. Es ermöglicht Forking PHP-Skripte und andere Skripte. Funktioniert auf UNIX und 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);
        }
    }
}

Dies ist die gleiche Methode, die ich jetzt für ein paar Jahre verwendet habe, und ich habe nicht oder besser etwas gesehen gefunden. Da die Menschen gesagt haben, PHP ist single threaded, so gibt es nicht viel was Sie tun können.

habe ich eigentlich eine zusätzliche Ebene dazugegeben und das ist immer und Speichern der Prozess-ID. Dies ermöglicht es mir, auf eine andere Seite umgeleitet werden und der Benutzer auf der Seite sitzen, mit AJAX zu überprüfen, ob der Vorgang abgeschlossen ist (Prozess-ID existiert nicht mehr). Dies ist nützlich für die Fälle, in denen die Länge des Skripts der Browser muss Timeout verursachen würde, aber der Benutzer warten, dass Skript vor dem nächsten Schritt abzuschließen. (In meinem Fall war es die Verarbeitung von großen ZIP-Dateien mit CSV wie Dateien, die in die Datenbank 30 000 Datensätze summieren sich nach denen der Benutzer einige Informationen bestätigen muss.)

Ich habe auch ein ähnliches Verfahren zur Erstellung von Berichten verwendet. Ich bin mir nicht sicher, ob ich „Hintergrundverarbeitung“ für etwas wie eine E-Mail verwenden würde, es sei denn, es ist ein echtes Problem mit einem langsamen SMTP ist. Stattdessen könnte ich eine Tabelle als eine Warteschlange verwenden und dann einen Prozess haben, der jede Minute läuft die E-Mails in der Warteschlange zu senden. Sie würden warry von E-Mail zweimal oder anderen ähnlichen Problemen beim Senden sein müssen. Ich würde auch einen ähnlichen Warteschlangenprozess für andere Aufgaben berücksichtigen.

PHP HAS Multithreading, es ist einfach nicht standardmäßig aktiviert ist, gibt es eine Erweiterung namens pThreads , das tut genau das. Sie werden php mit ZTS obwohl zusammengestellt müssen. (Threadsicher) Links:

Beispiele

Ein weiteres Tutorial

pThreads PECL-Erweiterung

Es ist eine großartige Idee cURL zu verwenden, wie von rojoca vorgeschlagen.

Hier ist ein Beispiel. Sie können text.txt überwachen, während das Skript im Hintergrund ausgeführt wird:

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

?>

Leider PHP hat keine Art von nativen Threading-Fähigkeiten. Also ich denke, in diesem Fall haben Sie keine andere Wahl, als eine Art von benutzerdefinierten Code zu verwenden, um zu tun, was Sie tun wollen.

Wenn Sie um das Netz zu suchen für PHP Sachen Einfädeln, haben einige Leute mit Möglichkeiten, um auf Threads auf PHP zu simulieren.

Wenn Sie die Content-Length HTTP-Header in Ihrer „Danke für die Registrierung“ gesetzt Antwort, dann sollte der Browser die Verbindung schließen, nachdem die angegebene Anzahl von Bytes empfangen werden. Dies läßt den serverseitige Prozess ausgeführt (unter der Annahme, dass ignore_user_abort gesetzt ist), so kann es beenden, ohne zu arbeiten, die Endbenutzer warten zu machen.

Natürlich müssen Sie die Größe Ihrer Antwortinhalt berechnen, bevor die Header-Rendering, aber das ist ziemlich einfach für kurze Antworten (Schreibausgang auf einen String, rufen strlen (), Call-header (), machen string).

Dieser Ansatz hat den Vorteil, nicht zwingt Sie, eine „Front-End“ Warteschlange zu verwalten, und obwohl Sie müssen möglicherweise einige Arbeit auf dem Back-End tun Rennen HTTP Kind-Prozesse von Schritt über zu verhindern jedes andere, das ist etwas, das Sie bereits tun musste, sowieso.

Wenn Sie nicht über den vollen geblasenen ActiveMQ wollen, empfehle ich beachten RabbitMQ . RabbitMQ ist leicht Messaging, das das AMQP Standard .

Wir empfehlen auch schauen Sie in php-amqplib - eine beliebte AMQP Client-Bibliothek AMQP zugreifen basierte Nachrichtenvermittler.

ich denke, sollten Sie diese Technik versuchen, wird es so viele, wie Seiten nennen helfen Ihnen alle Seiten gerne an läuft einmal unabhängig, ohne als asynchrone für jede Seite Antwort zu warten.

cornjobpage.php // Startseite

    <?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: Wenn Sie URL-Parameter als Schleife senden wollen, dann folgen Sie diese Antwort: https://stackoverflow.com/a/41225209 / 6295712

Die Laichen neue Prozesse auf dem Server exec() oder direkt auf einem anderen Server curl mit, dass auch nicht alle überhaupt nicht skalieren, wenn wir für exec gehen sind Sie im Grunde Ihren Servers mit langen laufenden Prozessen Füllung, die von anderen nicht behandelt werden kann Web verbundenen Servern und mit curl bindet einen anderen Server, wenn Sie in einer Art Lastenausgleich bauen.

I Gearman habe in einigen Situationen verwendet, und ich finde es besser, für diese Art von Anwendungsfall. Ich kann einen einzelnen Job-Queue-Server grundsätzlich behandeln aller Arbeitsplätze Warteschlangen, um von dem Server und Spin-up Arbeiter-Server durchgeführt werden, von denen jeder so viele Instanzen des Arbeitsprozesses führen kann je nach Bedarf und vergrößern die Anzahl der Arbeiter-Server je nach Bedarf und spinnt sie, wenn sie nicht benötigt werden. Lassen Sie mich auch ist vollständig die Arbeitsprozesse herunterzufahren, wenn nötig und reiht die Arbeitsplätze bis die Arbeiter wieder online sind.

ist PHP eine Single-Thread-Sprache, so gibt es keinen offiziellen Weg, um einen asynchronen Prozess mit ihm anders als mit exec oder popen zu starten. Es gibt eine Blog-Post über das hier . Ihre Idee für eine Warteschlange in MySQL ist eine gute Idee als gut.

Ihre spezifische Anforderung ist hier eine E-Mail an den Benutzer zu senden. Ich bin neugierig, warum Sie versuchen, die asynchron zu tun, da eine E-Mail eine ziemlich trivial und schnell ist Aufgabe auszuführen. Ich nehme an, wenn Sie Tonnen von E-Mail senden und Ihr ISP blockiert Sie wegen des Verdachts des Spamming, dass ein Grund sein könnte, in die Warteschlange, aber anders als das ich nicht aus irgendeinem Grund denken kann, es auf diese Weise zu tun.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top