Ich arbeite an einer etwas großen Webanwendung, und das Backend ist meistens in PHP. Es gibt mehrere Stellen im Code, an denen ich eine Aufgabe erledigen muss, aber ich möchte nicht, dass der Benutzer auf das Ergebnis wartet. Wenn Sie beispielsweise ein neues Konto erstellen, muss ich ihnen eine Begrüßungs-E-Mail senden. Wenn sie jedoch auf die Schaltfläche "Registrierung abschließen" klicken, möchte ich sie nicht warten lassen, bis die E-Mail tatsächlich gesendet wird. Ich möchte nur den Vorgang starten und dem Benutzer eine Nachricht zurückschicken.
Bisher habe ich an manchen Orten etwas benutzt, das sich wie ein Hack mit exec () anfühlt. Grundsätzlich Dinge zu tun wie:
exec("doTask.php $arg1 $arg2 $arg3 >/dev/null 2>&1 &");
Was scheint zu funktionieren, aber ich frage mich, ob es einen besseren Weg gibt. Ich überlege mir, ein System zu schreiben, das Aufgaben in einer MySQL-Tabelle in eine Warteschlange stellt, und ein separates PHP -Skript, das diese Tabelle einmal pro Sekunde abfragt und alle neuen gefundenen Aufgaben ausführt. Dies hätte auch den Vorteil, dass ich die Aufgaben in Zukunft auf mehrere Arbeitsmaschinen aufteilen könnte, wenn es nötig wäre.
Erneue ich das Rad neu? Gibt es eine bessere Lösung als der exec () - Hack oder die MySQL-Warteschlange?
Ich habe die Warteschlangenmethode verwendet, und es funktioniert gut, da Sie die Verarbeitung verschieben können, bis die Serverlast leer ist. Dadurch können Sie Ihre Last effektiv verwalten, wenn Sie "Aufgaben, die nicht dringend sind" problemlos partitionieren können.
Es ist nicht zu schwierig, selbst zu rollen, hier sind ein paar andere Optionen, die Sie ausprobieren sollten:
Eine andere, vielleicht einfachere Vorgehensweise ist die Verwendung von ignore_user_abort -. Sobald Sie die Seite an den Benutzer gesendet haben, können Sie die endgültige Verarbeitung ohne Angst vor einer vorzeitigen Beendigung durchführen. Dies hat jedoch den Effekt, dass die Seite verlängert wird Laden aus der Benutzersicht.
Wenn Sie nur eine oder mehrere HTTP-Anforderungen ausführen möchten, ohne auf die Antwort warten zu müssen, gibt es auch eine einfache PHP -Lösung.
Im aufrufenden 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
In der aufgerufenen script.php können Sie diese PHP - Funktionen in den ersten Zeilen aufrufen:
ignore_user_abort(true);
set_time_limit(0);
Dadurch wird das Skript ohne Zeitlimit weiter ausgeführt, wenn die HTTP-Verbindung geschlossen wird.
Eine andere Möglichkeit, Prozesse zu verzweigen, ist das Aufrollen. Sie können Ihre internen Aufgaben als Webservice einrichten. Zum Beispiel:
Dann rufen Sie in Ihrem Benutzer, auf den Skripts zugreifen, den Dienst auf:
$service->addTask('t1', $data); // post data to URL via curl
Ihr Dienst kann die Warteschlange von Aufgaben mit mysql verfolgen, oder was auch immer Ihnen gefällt: Der Dienst ist alles innerhalb des Dienstes und Ihr Skript verbraucht nur URLs. Dadurch können Sie den Dienst bei Bedarf auf einen anderen Computer/Server verschieben (dh leicht skalierbar).
Durch das Hinzufügen einer HTTP-Autorisierung oder eines benutzerdefinierten Autorisierungsschemas (wie z. B. die Web-Services von Amazon) können Sie Ihre Aufgaben für andere Personen/Services (wenn Sie möchten) verwenden, und Sie können es weiterführen und einen Überwachungsdienst hinzufügen, um den Überblick zu behalten Warteschlangen- und Taskstatus.
Es ist etwas Einrichtungsarbeit erforderlich, aber es gibt viele Vorteile.
Ich habe Beanstalkd für ein Projekt verwendet und habe es wieder geplant. Ich habe festgestellt, dass dies ein hervorragender Weg ist, um asynchrone Prozesse auszuführen.
Ein paar Dinge, die ich damit gemacht habe, sind:
Ich habe ein auf Zend-Framework basierendes System geschrieben, um eine 'Nice'-URL zu decodieren, um zum Beispiel die Größe eines Images zu ändern, würde es QueueTask('/image/resize/filename/example.jpg')
heißen. Die URL wurde zuerst in ein Array (Modul, Controller, Aktion, Parameter) dekodiert und anschließend in JSON konvertiert, um sie in die Warteschlange selbst einzuspeisen.
Ein lang laufendes cli-Skript hat dann den Job aus der Warteschlange geholt, ausgeführt (über Zend_Router_Simple) und, falls erforderlich, Informationen in memcached für die Website PHP abgelegt, um sie nach Bedarf abholen zu können.
Eine der Falten, die ich auch eingefügt habe, war, dass das cli-script vor dem Neustart nur für 50 Loops ausgeführt wurde. Wenn es jedoch wie geplant neu starten soll, würde dies sofort geschehen (über ein Bash-Script ausgeführt). Wenn es ein Problem gab und ich exit(0)
(den Standardwert für exit;
oder die();
) verwendet habe, würde es zunächst einige Sekunden anhalten.
Wenn es nur eine Frage der Bereitstellung teurer Aufgaben ist, im Falle von php-fpm, warum nicht die Funktion fastcgi_finish_request()
verwenden?
Diese Funktion löscht alle Antwortdaten an den Client und beendet die Anforderung. Dadurch können zeitaufwändige Aufgaben ausgeführt werden, ohne dass die Verbindung zum Client geöffnet bleibt.
Sie verwenden Asynchronität auf diese Weise nicht wirklich:
fastcgi_finish_request()
aus.Wieder ist php-fpm nötig.
Hier ist eine einfache Klasse, die ich für meine Webanwendung codiert habe. Es erlaubt, PHP Skripts und andere Skripte zu fälschen. Funktioniert unter 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 seit einigen Jahren verwende, und ich habe nichts Besseres gesehen oder gefunden. Wie die Leute gesagt haben, ist PHP ein einzelner Thread, es gibt also nicht mehr viel, was Sie tun können.
Ich habe tatsächlich eine zusätzliche Ebene hinzugefügt, und die Prozess-ID wird abgerufen und gespeichert. Dadurch kann ich auf eine andere Seite umleiten und den Benutzer auf dieser Seite sitzen lassen. Mit AJAX können Sie überprüfen, ob der Prozess abgeschlossen ist (Prozess-ID ist nicht mehr vorhanden). Dies ist nützlich in Fällen, in denen die Länge des Skripts zu einem Timeout des Browsers führen würde, der Benutzer jedoch warten muss, bis das Skript abgeschlossen ist, bevor der nächste Schritt ausgeführt wird. (In meinem Fall verarbeitete es große Zip-Dateien mit CSV-ähnlichen Dateien, die der Datenbank bis zu 30.000 Datensätze hinzufügen, woraufhin der Benutzer einige Informationen bestätigen muss.)
Ich habe auch einen ähnlichen Prozess zur Berichterstellung verwendet. Ich bin nicht sicher, ob ich "Hintergrundverarbeitung" für etwas wie eine E-Mail verwenden würde, es sei denn, es besteht ein echtes Problem mit einem langsamen SMTP. Stattdessen kann ich eine Tabelle als Warteschlange verwenden und dann einen Prozess ausführen, der jede Minute ausgeführt wird, um die E-Mails in der Warteschlange zu senden. Sie müssen sich darauf verlassen, dass Sie zweimal E-Mails senden oder ähnliche Probleme haben. Ich würde einen ähnlichen Warteschlangenprozess auch für andere Aufgaben in Betracht ziehen.
PHP HAT Multithreading, es ist einfach nicht standardmäßig aktiviert, es gibt eine Erweiterung namens pthreads, die genau das tut. (Thread Safe) Links:
Es ist eine gute Idee, cURL wie von rojoca vorgeschlagen zu verwenden.
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 hat PHP keine nativen Threading-Funktionen. Ich denke, in diesem Fall haben Sie keine andere Wahl, als einen benutzerdefinierten Code zu verwenden, um das zu tun, was Sie tun möchten.
Wenn Sie im Internet nach PHP Threading-Sachen suchen, haben einige Leute Möglichkeiten gefunden, Threads auf PHP zu simulieren.
Wenn Sie den Content-Length-HTTP-Header in Ihrer Antwort "Danke für die Registrierung" festlegen, sollte der Browser die Verbindung schließen, nachdem die angegebene Anzahl von Bytes empfangen wurde. Dadurch wird der serverseitige Prozess ausgeführt (vorausgesetzt, ignore_user_abort ist festgelegt), sodass die Arbeit beendet werden kann, ohne dass der Endbenutzer warten muss.
Natürlich müssen Sie vor dem Rendern der Header die Größe Ihres Antwortinhalts berechnen. Dies ist jedoch für kurze Antworten (Ausgabe in einen String schreiben, Aufruf strlen (), call header (), Renderstring) ziemlich einfach.
Dieser Ansatz hat den Vorteil, dass not Sie dazu zwingt, eine "Front-End" -Warteschlange zu verwalten, und obwohl Sie möglicherweise einige Arbeiten am Back-End durchführen müssen, um zu verhindern, dass laufende untergeordnete HTTP-Prozesse aufeinander treten, ist dies etwas, was Sie tun musste sowieso schon getan werden.
Wenn Sie nicht die volle ActiveMQ wollen, empfehle ich RabbitMQ . RabbitMQ ist ein leichtes Messaging, das den AMQP-Standard verwendet.
Ich empfehle einen Blick in php-amqplib - eine beliebte AMQP-Client-Bibliothek, um auf AMQP-basierte Nachrichtenbroker zuzugreifen.
ich denke, Sie sollten diese Technik ausprobieren, es wird hilfreich sein, so viele Seiten aufzurufen, wie Sie möchten, dass alle Seiten auf einmal unabhängig ausgeführt werden, ohne auf jede Antwort als asynchron zu warten.
cornjobpage.php // Hauptseite
<?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 möchten, folgen Sie dieser Antwort: https://stackoverflow.com/a/41225209/6295712
Das Erstellen neuer Prozesse auf dem Server mit exec()
oder direkt auf einem anderen Server mit curl lässt sich nicht so gut skalieren. Wenn Sie sich für exec entscheiden, füllen Sie Ihren Server mit langwierigen Prozessen auf, die von anderen Servern ohne Webzugriff verarbeitet werden können und curl bindet einen anderen Server an, es sei denn, Sie bauen eine Art Lastverteilung ein.
Ich habe Gearman in einigen Situationen verwendet und finde es für diese Art von Anwendungsfall besser. Ich kann einen einzigen Job-Warteschlangenserver verwenden, um im Wesentlichen die Warteschlangen aller Jobs, die vom Server erledigt werden müssen, sowie die Hochlauf-Worker-Server zu handhaben, von denen jeder beliebig viele Instanzen des Worker-Prozesses ausführen und die Anzahl der Server erhöhen kann Worker-Server nach Bedarf und drehen sie herunter, wenn sie nicht benötigt werden. Außerdem kann ich die Arbeitsprozesse bei Bedarf vollständig herunterfahren und die Jobs in die Warteschlange stellen, bis die Arbeiter wieder online sind.