• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

PHP beschleunigen

braegler

Aktiver NGBler

Registriert
14 Juli 2013
Beiträge
904
Morgen zusammen,

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

Eines meiner Freizeitprojekte 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: https://github.com/xPaw/PHP-Source-Query-Class )
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.
 

alter_Bekannter

N.A.C.J.A.C.

Registriert
14 Juli 2013
Beiträge
4.824
Ort
Midgard
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.
 

keksautomat

Neu angemeldet

Registriert
15 Juli 2013
Beiträge
471
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 PHP Hiphop. (damit habe ich noch nie was gemacht)
 

accC

gesperrt

Registriert
14 Juli 2013
Beiträge
5.250
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. (klick könnte ganz interessant für dich sein)
Allgemein könnte auch dieser Artikel interessant sein: klick härter
Wie gesagt, würde ich da allerdings Abstand von PHP nehmen. Spricht denn etwas gegen ein kleines Java Konsolenprogramm?
 

alter_Bekannter

N.A.C.J.A.C.

Registriert
14 Juli 2013
Beiträge
4.824
Ort
Midgard
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?
 

braegler

Aktiver NGBler

Registriert
14 Juli 2013
Beiträge
904
  • 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 :)
 

drfuture

Zeitreisender
Teammitglied

Registriert
14 Juli 2013
Beiträge
8.754
Ort
in der Zukunft
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 http://de.wikipedia.org/wiki/HipHop 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.
 

accC

gesperrt

Registriert
14 Juli 2013
Beiträge
5.250
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?
 

drfuture

Zeitreisender
Teammitglied

Registriert
14 Juli 2013
Beiträge
8.754
Ort
in der Zukunft
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
 

braegler

Aktiver NGBler

Registriert
14 Juli 2013
Beiträge
904
  • 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 ;)
 

accC

gesperrt

Registriert
14 Juli 2013
Beiträge
5.250
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..
 

Kugelfisch

Nerd

Registriert
12 Juli 2013
Beiträge
2.342
Ort
Im Ozean
Bei TF2 ruft der Scanner besagtes externes Skript auf
PHP:
// 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:
<?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";
                }
        }
}
?>
 
Oben