PHP beschleunigen

braegler

NGBler
Registriert
14 Juli 2013
Beiträge
873
Morgen zusammen,

Ich hab da ein Problem eine Herausforderung an der ich Euch gern teilhaben lassen würde.

Eines meiner hat vor wenigen Tagen eine Erweiterung erfahren.
Dabei stosse ich, langsam aber sich, an die Grenzen meiner Serverkapazität und erst Recht meines KnowHows.

Ausgangslage:
Seit einigen Tagen habe ich ein Tracking für Team-Fortress 2 Gameserver am Laufen.
Bei den bisherigen Games konnte alles innerhalb des Skriptes abgehandelt werden
(UPD-Packet an Gameserver senden, auf Antwort warten, parsen. Das wurde so realisiert, dass die Aufrufe nicht blockierend sind)
Nun ist bei TF2 das Query Format etwas komplizierter.
UDP-Challenge Request -> Antwort -> UDP Info Request (mit Challenge-Daten) -> UDP Player Request (mit Challenge Daten) -> parsen.

Da ich das ganze nicht non-blocking verwirklichen konnte, habe ich dies in ein extra Skript ausgelagert, und eine Query-Klasse (dieser sehr ähnlich: )
herangezogen. Dieses Skript erzeugt ein Array mit allen Informationen eines Gameservers und gibt diese serialisiert aus (echo)

Die Server sollen regelmässig abgefragt werden. Innerhalb des Hauptskriptes wird durch die Serverliste iteriert und das extra-Skript mittels eines Konstrukts wie "exec (php SCRIPT > TmpFileAufRAMFS)" aufgerufen.
Sobald die entsprechende PID nicht mehr vorhanden ist (sprich: Das Query-Skript hat seine Arbeit getan und die Ausgabe in dem entsprechenden TmpFile [serverspezifisch] gespeichert), wird die Datei vom Hauptskript wieder eingelesen, geparst und die Daten in der Mysql Datenbank gespeichert. Das ganze läuft somit asynchron, nicht blockierend.

Herausforderung:
Momentan schaffe ich es, etwa 1200 Server pro Scanrunde (60s) abzuscannen.
Diesen Count möchte ich "etwas" nach Oben bringen (Ziel: >2000).

Nun habe ich mich etwas "schlau" gemacht, und bin auf die Idee gekommen (keine Ahnung ob diese gut ist) dieses externe Skript mittels pre-Compiling zu beschleunigen.
Allerdings bin ich, was dieses angeht, absolut und 100%ig unbedarft, hab noch nie damit gearbeitet.

Hat jemand von Euch irgendwelche Tipps wie ich eine solche Ausführung tunen kann, prinzipiell bin ich zu allen Schandtaten bereit. :beer:

Vielleicht noch etwas Info:
Der Server läuft auf Debian 6, php in Version PHP 5.3.3-7 , als CLI. Ein Update auf php >= 5.4 fällt leider flach, da die ganze Seite einige tausend Funktionsaufrufe mit Call-time pass-by-reference hat.

Ich bin Euch für jeden Tipp dankbar.
 
Durch diese Art von Skriptaufruf geht in der Tat eine Menge Zeit dadurch verloren das jedes mal neu kompiliert werden muss.

Die Anzahl der Skriptinstanzen durch die das ganze durchgereicht wird zu reduzieren würde da allerdings auch helfen wenn eh alles PHP ist.

Sprich, kein Exec mehr sondern Include und die entsprechenden Funktionen bei Bedarf aufrufen.
 
Evtl. würde einfach auch schon Multithreading helfen.
Dh. du schickst mehrere Instanzen los (und setzt pro Server eine Lock Datei(?)). Mehrere Instanzen = höhere Last, aber schneller am Ziel.

Oder du nutzt (damit habe ich noch nie was gemacht)
 
Ich würde von PHP Abstand nehmen, wenn du wirklich auf Performance trimmen möchtest.
Bezüglich keksautomat's Vorschlag: PHP müsstest du für Multithreading ggf. neu installieren. ( könnte ganz interessant für dich sein)
Allgemein könnte auch dieser Artikel interessant sein:
Wie gesagt, würde ich da allerdings Abstand von PHP nehmen. Spricht denn etwas gegen ein kleines Java Konsolenprogramm?
 
Verringert die Verzögerung die mein Ansatz komplett eliminiert, allerdings weiss ich nicht mit wie viel Aufwand.

Die Unterschiede sollen zwar ganz erheblich sein, angeblich bis zu 90% aber warum 90% statt 100% nehmen?
Allerdings hab ich keine Ahnung vom Umsetzungsaufwand, müssen Skripte in der Praxis dafür geändert werden?
Was muss alles beachtet werden?
 
  • Thread Starter Thread Starter
  • #9
Erstmal danke für die Antworten.

Die einzelnen Aufrufe des Query Skripts laufen mit &, um den Prozess in den Hintergrund zu bringen.
Synchron würde ich gerade mal 90 Server pro Minute schaffen, da im Schnitt durchaus 0.5s für die Ausführung der upd Kommunikation drauf gehen.

Das Inkludieren hatte ich zuerst,aber es ist mir nicht gelungen, das Query Skript so anzupassen, dass es nicht blockierend auftritt.
Für die einfachen Server auf Quake3 Basis ist das, selbst für mich, kein Problem.

Ein einzelner Scan besteht aus
_SendRequest (öffnet den Socket,sende chr(255)chr(255)chr(255)chr(255)getstatus)
_Wait4Answer (überprüft ob am Socket Antwort Daten anstehen, wenn dem so ist => true
_Parse (liest den Socket aus, und parst diese)
Die Parent Funktion startet _SendRequest, prüft in Schleife ob _Wait4Answer True ist, wenn dann wird _Parse aufgerufen.

Das oben verlinkte Steam-Query Skript jedoch so zu splitten, dass es nicht blockierend funktioniert ist mir jedoch nicht gelungen.
Deshalb hab ich das Auslesen und serialisierte Speichern in einer Datei über ein separates Skript als Umweg in Kauf genommen.

pThreads ist leider momentan nicht installiert, fork habe ich mal versucht, jedoch hatte ich da auch wieder das Problem der Datenübergabe.
(Hauptskript ist ein Tracker.php Skript, welches Zyklisch die einzelnen Instanzen der Serverklasse erstellt und in den Scan schickt, nach erfolgtem Scan wird der geparse Serverinfo-String vom Tracker-Skript in die Verarbeitung geschickt ,spricht Datenbank Aktionen usw. Danach wird die Instanz zerstört).

Java ist für mich ein Buch mit sieben Siegeln. Bin froh dass ich mich nach VB6 und Bash in php (einigermassen) Zurecht finde.

Ich werde mir aber auf jeden Fall mal die HHVM anschauen, auch wenn ich das als nicht zeitnah realisierbar für mich empfinde ;)

Über weitere Anregungen würde ich mich freuen :)
 
Nun ja bei solchen Problemen schaut man ja am besten mal bei denen Vorbei die ähnliche Probleme im großen stiel haben *Fg*
Facebook dürfte zu den größten Seiten auf basise von PHP sein.

HHVM hast du ja schon gefunden - für einen einfacheren Ansatz in deinem Fall ist vielleicht besser.

Auch lässt sich php als Dienst betreiben der an einem Socket horcht - was vielleicht ein immer neues starten des compilers insgesamt spart - wenn auch den compiler-vorgang insgesammt nicht.
 
Nun ja bei solchen Problemen schaut man ja am besten mal bei denen Vorbei die ähnliche Probleme im großen stiel haben *Fg*
Facebook dürfte zu den größten Seiten auf basise von PHP sein.
Bist du dir sicher, dass sich facebook mit PHP rumschlägt? Ich meine klar, für einige kleinere Spielereien sicherlich, aber afaik steht hinter dem Facebook-Kern kein PHP.

Gut, wenn du so gar keine Ahnung von Java hast, ist das sicherlich nicht optimal.

Sehe ich das richtig, dass du die Daten zunächst vom Gameserver abgreifst, in eine Datenbank wirfst und dann wieder heraus fischst, um sie auszugeben?
 
jo @accC Facebook wurde damals vom lieben Marc in php entwickelt... und wird in weiten teilen noch immer damit betrieben.
Da es damit in der menge von Seitenimpressionen natürlich probleme gab - wurden ja ein paar Verbesserungen entwickelt ;)

Einige Funktionen werden aber wohl seit einiger Zeit in div. c++ Bibliotheken ausgelagert... gut was es nun im Kern ist ist die Frage - so 100% infos wirds da wohl nicht Jahresaktuell in der öffentlichkeit geben :D
 
  • Thread Starter Thread Starter
  • #13
@accC: Das ganze besteht im Prinzip aus Tracker und Scanner.
Der Tracker ist für die Verarbeitung der Daten zuständig, der Scanner (inkludiert in den Tracker) liefert die Serverdaten.
Der Scanner funktioniert bei Q3 non-blocking.
Bei TF2 ruft der Scanner besagtes externes Skript auf
// so in etwa
exec ( "php externesSkript serverip:port > outfile & echo $! >pidfile");
outfile und pidfile liegenauf einem RamFS
Der Scanner (inkl.) prüft nun ob die im pidfile gespeicherte PID noch aktiv is (existiert /proc/$pid [in besagter _Wait4Answer]), ist das nicht der Fall wird die outfile eingelesen, unserialisiert und ins Scanner Objekt eingelesen.
Der Tracker bekommt dann den Call die Daten zu verarbeiten, also DB Updates, Inserts usw.
Mysqld ist zwar gut beschäfftigt (> 2000qps , 92% writes, 8%reads) aber nicht das Bottleneck, geschweige denn am Limit.

HipHop selbst finde ich niergendwo mehr, alles verweist auf HHVM, was für ein einzelnes Skript vielleicht ein wenig oversized ist.
DrFuture, hast Du zufällig irgendwas in der Hinterhand ;)
 
Mysqld ist zwar gut beschäfftigt (> 2000qps , 92% writes, 8%reads) aber nicht das Bottleneck, geschweige denn am Limit.
Klar, dass der SQL-Server das bottleneck ist, wollte ich nicht sagen, nur verlierst du eben durch das zig fache Übergeben immer wieder Zeit.

php <- sh
disc <- sh
php <- disc
php <- sh
sql <- php
php <- sql
...

Ich bin mir nicht sicher, aber gehe davon aus, dass du eine höhere Performance erreichen könntest, würdest du dir ein paar Wege sparen..
 
Bei TF2 ruft der Scanner besagtes externes Skript auf
PHP:
Expand Collapse Copy
// so in etwa
exec ( "php externesSkript serverip:port > outfile & echo $! >pidfile");
Das ist, da offenbar für jeden zu überprüfenden Server eine Skriptinstanz gestartet wird, ein sehr ineffizientes Vorgehen. Sinnvoller wäre, alle Server in einem Skript zu überprüfen. Multithreading ist dazu nicht erforderlich (und verursacht bloss unnötigen Overhead); damit du mehrere Verbindungen gleichzeitig bedienen kannst und das Warten auf eine Antwort dein Skript nicht blockiert, musst du die Sockets nicht-blockierend nutzen. Zum Beispiel:
PHP:
Expand Collapse Copy
<?php

error_reporting(E_ALL);
for($i=0; $i<100; $i++) {
        $sockets[] = fsockopen('udp://127.0.0.1', 42000+$i);
}


foreach($sockets as $socket) {
        stream_set_blocking($socket, 0);
        stream_set_read_buffer($socket, 0);
        fwrite($socket,"Nachricht\n");
}

$start = time();
while(time()-$start < 30) {  // 30 sec = timeout
        $read_ready = $sockets;
        $write_ready = $except = NULL;
        stream_select($read_ready,$write_ready,$except,1);

        foreach($read_ready as $idx => $socket) {
                if(($read = fread($socket,1024)) != '') {
                        echo "Socket #$idx: '$read'\n";
                }
        }
}
?>
 
Zurück
Oben