PHP ist allgegenwärtig und ist wohl die Sprache, die am häufigsten im Internet eingesetzt wird.

Allerdings ist sie nicht gerade für ihre Hochleistungsfähigkeiten bekannt, insbesondere wenn es um hochgradig nebenläufige Systeme geht. Und das ist der Grund, warum Sprachen wie Node (ja, ich weiß, es ist keine Sprache), Go und Elixir für solche speziellen Anwendungsfälle immer mehr an Bedeutung gewinnen.

Dennoch gibt es eine Menge, was Sie tun können, um die PHP-Leistung auf Ihrem Server zu verbessern. Dieser Artikel konzentriert sich auf die php-fpm Seite, die Sie natürlich auf Ihrem Server konfigurieren müssen, wenn Sie Nginx verwenden.

Falls Sie wissen, was php-fpm ist, können Sie gerne zu dem Abschnitt über die Optimierung springen.

Was ist PHP-fpm?

Nicht viele Entwickler interessieren sich für DevOps, und selbst von denen, die sich dafür interessieren, wissen nur sehr wenige, was unter der Haube vor sich geht. Interessanterweise ist es nicht PHP, das den ersten Kontakt herstellt, wenn der Browser eine Anfrage an einen Server sendet, auf dem PHP läuft, sondern der HTTP-Server, von denen die wichtigsten Apache und Nginx sind. Diese “Webserver” müssen dann entscheiden, wie sie sich mit PHP verbinden und den Anfragetyp, die Daten und die Kopfzeilen an PHP weiterleiten.

request-response-in-php-e1542398057451
Der Anfrage-Antwort-Zyklus im Fall von PHP (Bildnachweis: ProinerTech)

In modernen PHP-Anwendungen ist der obige Teil “Datei finden” die index.php, die so konfiguriert ist, dass der Server alle Anfragen an sie weiterleitet.

Wie genau der Webserver die Verbindung zu PHP herstellt, hat sich weiterentwickelt, und dieser Artikel würde in seiner Länge explodieren, wenn wir auf alle Einzelheiten eingehen würden. Aber grob gesagt war PHP in der Zeit, als Apache der Webserver der Wahl war, ein in den Server integriertes Modul.

Wenn also eine Anfrage eintraf, startete der Server einen neuen Prozess, der automatisch PHP einschloss und es ausführen ließ. Diese Methode wurde mod_php genannt, kurz für “PHP as a module” Dieser Ansatz hatte seine Grenzen, die Nginx mit php-fpm überwunden hat.

Bei php-fpm liegt die Verantwortung für die Verwaltung der PHP-Prozesse bei dem PHP-Programm auf dem Server. Mit anderen Worten, dem Webserver (in unserem Fall Nginx) ist es egal, wo sich PHP befindet und wie es geladen wird, solange er weiß, wie er Daten senden und von ihm empfangen kann. Wenn Sie möchten, können Sie sich PHP in diesem Fall als einen weiteren Server vorstellen, der einige untergeordnete PHP-Prozesse für eingehende Anfragen verwaltet (die Anfrage erreicht also einen Server, wird von einem Server empfangen und an einen Server weitergeleitet – ziemlich verrückt! :-P).

Wenn Sie schon einmal Nginx eingerichtet haben oder auch nur ein wenig darin herumgestöbert haben, werden Sie auf etwas wie dieses stoßen:

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(. .php)(/. )$;
        fastcgi_pass unix:/run/php/php7.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

Die Zeile, die uns interessiert, ist diese: fastcgi_pass unix:/run/php/php7.2-fpm.sock;, die Nginx anweist, mit dem PHP-Prozess über den Socket namens php7.2-fpm.sock zu kommunizieren. Bei jeder eingehenden Anfrage schreibt Nginx also Daten über diese Datei und sendet sie nach Erhalt der Ausgabe zurück an den Browser.

Ich muss noch einmal betonen, dass dies nicht das vollständigste oder genaueste Bild der Vorgänge ist, aber für die meisten DevOps-Aufgaben ist es völlig ausreichend.

Lassen Sie uns nun rekapitulieren, was wir bisher gelernt haben:

  • PHP empfängt die von den Browsern gesendeten Anfragen nicht direkt. Webserver wie Nginx fangen diese zunächst ab.
  • Der Webserver weiß, wie er sich mit dem PHP-Prozess verbinden kann, und gibt alle Anfragedaten an PHP weiter (er fügt buchstäblich alles ein).
  • Wenn PHP mit seiner Arbeit fertig ist, sendet es die Antwort an den Webserver zurück, der sie wiederum an den Client (oder Browser, in den meisten Fällen) weiterleitet.

Oder grafisch:

php-and-nginx
Wie PHP und Nginx zusammenarbeiten (Bildnachweis: DataDog)

Soweit so gut, aber jetzt kommt die Millionen-Dollar-Frage: Was genau ist PHP-FPM?

Das “FPM” in PHP steht für “Fast Process Manager”, was nichts anderes bedeutet, als dass das auf einem Server laufende PHP kein einzelner Prozess ist, sondern mehrere PHP-Prozesse, die von diesem FPM-Prozessmanager gestartet, gesteuert und beendet werden. Es ist dieser Prozessmanager, an den der Webserver die Anfragen weiterleitet.

Der PHP-FPM ist ein ganzes Kaninchenloch für sich, also erforschen Sie ihn ruhig, wenn Sie möchten, aber für unsere Zwecke reicht diese Erklärung aus 🙂

Warum PHP-fpm optimieren?

Warum sollten Sie sich über all diese Dinge Gedanken machen, wenn alles gut funktioniert? Warum lassen Sie die Dinge nicht einfach so, wie sie sind.

Ironischerweise ist das genau der Rat, den ich für die meisten Anwendungsfälle gebe. Wenn Ihre Einrichtung gut funktioniert und es keine außergewöhnlichen Anwendungsfälle gibt, verwenden Sie die Standardeinstellungen. Wenn Sie jedoch über einen einzelnen Rechner hinaus skalieren möchten, ist es wichtig, das Maximum aus einem Rechner herauszuholen, da dies die Serverkosten um die Hälfte (oder sogar mehr!) reduzieren kann.

Ein weiterer wichtiger Punkt ist, dass Nginx für die Bewältigung großer Arbeitslasten entwickelt wurde. Es ist in der Lage, Tausende von Verbindungen gleichzeitig zu verarbeiten, aber wenn das nicht auch auf Ihre PHP-Einrichtung zutrifft, verschwenden Sie nur Ressourcen, da Nginx darauf warten muss, dass PHP den aktuellen Prozess beendet und den nächsten annimmt, was die Vorteile von Nginx endgültig zunichte macht!

Nachdem das geklärt ist, wollen wir uns nun ansehen, was genau wir ändern würden, wenn wir versuchen, php-fpm zu optimieren.

Wie optimiert man PHP-FPM?

Der Speicherort der Konfigurationsdatei für php-fpm kann je nach Server unterschiedlich sein, so dass Sie etwas suchen müssen. Unter UNIX können Sie den Befehl find verwenden. Auf meinem Ubuntu lautet der Pfad /etc/php/7.2/fpm/php-fpm.conf. Die 7.2 ist natürlich die Version von PHP, die ich verwende.

So sehen die ersten Zeilen dieser Datei aus:

;;;;;;;;;;;;;;;;;;;;;
fPM-Konfiguration ;
;;;;;;;;;;;;;;;;;;;;;

alle relativen Pfade in dieser Konfigurationsdatei sind relativ zum PHP-Installations
; Präfix (/usr). Dieser Präfix kann dynamisch mit dem Argument
; Argument '-p' auf der Kommandozeile dynamisch geändert werden.

;;;;;;;;;;;;;;;;;;
; Globale Optionen ;
;;;;;;;;;;;;;;;;;;

[global]
; Pid-Datei
; Hinweis: Das Standardpräfix ist /var
; Standardwert: keine
pid = /run/php/php7.2-fpm.pid

; Fehlerprotokolldatei
wenn sie auf "syslog" gesetzt ist, wird das Protokoll an syslogd gesendet, anstatt in eine lokale Datei ; geschrieben zu werden
; in eine lokale Datei geschrieben.
; Hinweis: Das Standardpräfix ist /var
; Standardwert: log/php-fpm.log
error_log = /var/log/php7.2-fpm.log

Ein paar Dinge sollten sofort ins Auge fallen: Die Zeile pid = /run/php/php7.2-fpm.pid sagt uns, welche Datei die Prozess-ID des php-fpm-Prozesses enthält.

Wir sehen auch, dass /var/log/php7.2-fpm.log der Ort ist, an dem php-fpm seine Protokolle speichern wird.

Fügen Sie in dieser Datei drei weitere Variablen wie folgt hinzu:

emergency_restart_threshold 10
notfall_neustart_intervall 1m
process_control_timeout 10s

Die ersten beiden Einstellungen dienen der Vorsicht und teilen dem php-fpm Prozess mit, dass der Hauptprozess von php-fpm neu gestartet werden soll, wenn zehn Kindprozesse innerhalb einer Minute ausfallen.

Das hört sich vielleicht nicht sehr robust an, aber PHP ist ein kurzlebiger Prozess, der viel Speicher verbraucht, so dass ein Neustart des Hauptprozesses bei einem hohen Fehleraufkommen eine Menge Probleme lösen kann.

Die dritte Option, process_control_timeout, weist die Kindprozesse an, so lange zu warten, bevor sie das vom Elternprozess empfangene Signal ausführen. Dies ist in Fällen nützlich, in denen die Kindprozesse gerade etwas tun, wenn der Elternprozess z.B. ein KILL-Signal sendet. Wenn sie zehn Sekunden Zeit haben, haben sie eine bessere Chance, ihre Aufgaben zu beenden und sich ordnungsgemäß zu beenden.

Überraschenderweise ist dies nicht der Kern der php-fpm Konfiguration! Das liegt daran, dass php-fpm für die Bearbeitung von Webanfragen einen neuen Pool von Prozessen erstellt, für den eine eigene Konfiguration gilt. In meinem Fall war der Name des Pools www und die Datei, die ich bearbeiten wollte, war /etc/php/7.2/fpm/pool.d/www.conf.

Lassen Sie uns sehen, wie diese Datei beginnt:

; Starten Sie einen neuen Pool mit dem Namen 'www'.
; die Variable $pool kann in jeder Direktive verwendet werden und wird durch den
; Pool-Namen ('www' hier)
[www]

; Pro Pool-Präfix
; Sie gilt nur für die folgenden Direktiven:
; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
; - 'chdir'
; - 'php_values'
; - 'php_admin_values'
wenn nicht gesetzt, gilt stattdessen das globale Präfix (oder /usr).
; Hinweis: Diese Direktive kann auch relativ zum globalen Präfix gesetzt werden.
; Standardwert: keine
präfix = /pfad/zu/pools/$pool

unix-Benutzer/Gruppe von Prozessen
hinweis: Der Benutzer ist obligatorisch. Wenn die Gruppe nicht angegeben wird, wird die Gruppe des Standardbenutzers
; verwendet.
benutzer = www-data
gruppe = www-data

Ein kurzer Blick auf das Ende des obigen Schnipsels löst das Rätsel, warum der Serverprozess als www-data läuft. Wenn Sie bei der Einrichtung Ihrer Website Probleme mit den Dateiberechtigungen hatten, haben Sie wahrscheinlich den Eigentümer oder die Gruppe des Verzeichnisses auf www-data geändert, so dass der PHP-Prozess in die Protokolldateien schreiben und Dokumente hochladen kann usw.

Schließlich kommen wir zum Kern des Problems, der Einstellung des Prozessmanagers (pm). In der Regel sehen die Standardeinstellungen etwa so aus:

pm = dynamisch
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200

Was bedeutet also“dynamisch” hier? Ich denke, die offiziellen Dokumente erklären dies am besten (ich meine, dies sollte bereits Teil der Datei sein, die Sie bearbeiten, aber ich habe es hier nur für den Fall wiedergegeben, dass dies nicht der Fall ist):

; Wählen Sie, wie der Prozessmanager die Anzahl der Kindprozesse kontrollieren soll.
; Mögliche Werte:
; statisch - eine feste Anzahl (pm.max_children) von Kindprozessen;
; dynamisch - die Anzahl der Kindprozesse wird dynamisch festgelegt, basierend auf den
; folgenden Direktiven. Bei dieser Prozessverwaltung gibt es
; immer mindestens 1 Kindprozess.
pm.max_children - die maximale Anzahl von Kindprozessen, die ; gleichzeitig aktiv sein können
; gleichzeitig aktiv sein können.
pm.start_servers - die Anzahl der Kinder, die beim Start ; erzeugt werden.
pm.min_spare_servers - die minimale Anzahl von Kindern im 'idle' ; Zustand (warten auf Verarbeitung)
; Zustand (wartet auf Verarbeitung). Wenn die Anzahl
; der 'Idle'-Prozesse geringer ist als diese
anzahl, dann werden einige Kindprozesse ; erstellt.
pm.max_spare_servers - die maximale Anzahl von Kindern im ; Idle-Zustand (warten auf Bearbeitung)
; Status (wartet auf Verarbeitung). Wenn die Anzahl
; der 'Idle'-Prozesse größer ist als diese
; Zahl, werden einige Kinder getötet.
; ondemand - beim Start werden keine Kindprozesse ; erstellt. Kinder werden gegabelt, wenn
; neue Anfragen eine Verbindung herstellen werden. Die folgenden Parameter werden verwendet:
; pm.max_children - die maximale Anzahl von Kindern, ; die gleichzeitig
; gleichzeitig aktiv sein können.
pm.process_idle_timeout - Die Anzahl der Sekunden, nach denen
; ein inaktiver Prozess beendet wird.
; Hinweis: Dieser Wert ist obligatorisch.

Wir sehen also, dass es drei mögliche Werte gibt:

  • Statisch: Eine feste Anzahl von PHP-Prozessen wird beibehalten, egal was passiert.
  • Dynamisch: Sie können die minimale und maximale Anzahl der Prozesse festlegen, die php-fpm zu einem bestimmten Zeitpunkt aufrechterhält.
  • auf Anfrage: Prozesse werden auf Abruf erstellt und zerstört.

Welche Bedeutung haben diese Einstellungen also?

Einfach ausgedrückt: Wenn Sie eine Website mit geringem Datenverkehr haben, ist die Einstellung “dynamisch” in den meisten Fällen eine Verschwendung von Ressourcen. Angenommen, Sie haben pm.min_spare_servers auf 3 eingestellt, dann werden drei PHP-Prozesse erstellt und aufrechterhalten, auch wenn auf der Website kein Datenverkehr herrscht. In solchen Fällen ist “ondemand” die bessere Option, da das System entscheidet, wann neue Prozesse gestartet werden.

Auf der anderen Seite werden Websites, die ein hohes Verkehrsaufkommen haben oder schnell reagieren müssen, in dieser Einstellung bestraft. Einen neuen PHP-Prozess zu erstellen, ihn in einen Pool einzubinden und ihn zu überwachen, ist ein zusätzlicher Overhead, den Sie besser vermeiden sollten.

Die Verwendung von pm = static legt die Anzahl der Kindprozesse fest, so dass die maximalen Systemressourcen für die Bedienung der Anfragen und nicht für die Verwaltung von PHP verwendet werden können. Wenn Sie sich für diesen Weg entscheiden, sollten Sie beachten, dass er seine Richtlinien und Fallstricke hat. Ein recht ausführlicher, aber sehr nützlicher Artikel darüber finden Sie hier.

Letzte Worte

Da Artikel über Web-Performance Kriege entfachen oder zur Verwirrung beitragen können, halte ich ein paar Worte für angebracht, bevor wir diesen Artikel schließen. Bei der Leistungsoptimierung geht es ebenso sehr um Vermutungen und dunkle Künste wie um Systemwissen.

Selbst wenn Sie alle php-fpm-Einstellungen auswendig kennen, ist der Erfolg nicht garantiert. Wenn Sie keine Ahnung von der Existenz von php-fpm haben, brauchen Sie keine Zeit damit zu verschwenden, sich darüber Gedanken zu machen. Machen Sie einfach weiter mit dem, was Sie bereits tun, und machen Sie weiter.

Vermeiden Sie gleichzeitig, ein Performance-Junkie zu werden. Ja, Sie können eine noch bessere Leistung erzielen, indem Sie PHP von Grund auf neu kompilieren und alle Module entfernen, die Sie nicht verwenden werden, aber dieser Ansatz ist in Produktionsumgebungen nicht vernünftig genug. Die ganze Idee der Optimierung besteht darin, zu prüfen, ob Ihre Bedürfnisse von den Standardeinstellungen abweichen (was selten der Fall ist!), und bei Bedarf kleinere Änderungen vorzunehmen.

Wenn Sie nicht bereit sind, Zeit in die Optimierung Ihrer PHP-Server zu investieren, sollten Sie eine zuverlässige Plattform wie Kinsta nutzen, die sich um die Leistungsoptimierung und Sicherheit kümmert.