
Die Tests wurden mit Yandex Tank durchgeführt.
Symfony 4 und PHP 7.2 wurden als Anwendung verwendet.
Ziel war es, die Eigenschaften von Diensten bei unterschiedlichen Belastungen zu vergleichen und die beste Option zu finden.
Der Einfachheit halber wird alles in Docker-Containern gesammelt und mit Docker-Compose angehoben.
Unter einer Katze gibt es viele Tabellen und Grafiken.
Der Quellcode ist hier .
Alle im Artikel beschriebenen Beispiele für Befehle sollten aus dem Projektverzeichnis ausgeführt werden.
App
Die Anwendung läuft unter Symfony 4 und PHP 7.2.
Beantwortet nur eine Route und kehrt zurück:
- Zufallszahl;
- Umwelt
- pid des Prozesses;
- Name des Dienstes, mit dem es arbeitet;
- php.ini Variablen.
Antwortbeispiel:
curl 'http://127.0.0.1:8000/' | python -m json.tool { "env": "prod", "type": "php-fpm", "pid": 8, "random_num": 37264, "php": { "version": "7.2.12", "date.timezone": "Europe/Paris", "display_errors": "", "error_log": "/proc/self/fd/2", "error_reporting": "32767", "log_errors": "1", "memory_limit": "256M", "opcache.enable": "1", "opcache.max_accelerated_files": "20000", "opcache.memory_consumption": "256", "opcache.validate_timestamps": "0", "realpath_cache_size": "4096K", "realpath_cache_ttl": "600", "short_open_tag": "" } }
PHP ist in jedem Container konfiguriert:
- OPcache ist aktiviert;
- konfigurierter Bootstrap-Cache mit Composer;
- Die Einstellungen für php.ini entsprechen den Best Practices von Symfony .
Protokolle werden in stderr geschrieben:
/config/packages/prod/monolog.yaml
monolog: handlers: main: type: stream path: "php://stderr" level: error console: type: console
Der Cache ist in / dev / shm geschrieben:
/src/Kernel.php
... class Kernel extends BaseKernel { public function getCacheDir() { if ($this->environment === 'prod') { return '/dev/shm/symfony-app/cache/' . $this->environment; } else { return $this->getProjectDir() . '/var/cache/' . $this->environment; } } } ...
Jeder Docker-Compose startet drei Hauptcontainer:
- Nginx - Reverse-Proxy-Server;
- App-vorbereiteter Anwendungscode mit allen Abhängigkeiten;
- PHP FPM \ Nginx Unit \ Road Runner \ React PHP - Anwendungsserver.
Die Anforderungsverarbeitung ist auf zwei Anwendungsinstanzen beschränkt (durch die Anzahl der Prozessorkerne).
Dienstleistungen
PHP-Prozessmanager. Geschrieben in C.
Vorteile:
- keine Notwendigkeit, den Speicher im Auge zu behalten;
- Sie müssen nichts an der Anwendung ändern.
Nachteile:
- PHP muss Variablen für jede Anforderung initialisieren.
Befehl zum Starten der Anwendung mit Docker-Compose:
cd docker/php-fpm && docker-compose up -d
PHP-Prozessmanager. Es ist in PHP geschrieben.
Vorteile:
- initialisiert die Variablen einmal und verwendet sie dann;
- Sie müssen nichts an der Anwendung ändern (es gibt vorgefertigte Module für Symfony / Laravel, Zend, CakePHP).
Nachteile:
- müssen der Erinnerung folgen.
Befehl zum Starten der Anwendung mit Docker-Compose:
cd docker/php-ppm && docker-compose up -d
Anwendungsserver vom Nginx-Team. Geschrieben in C.
Vorteile:
- Sie können die Konfiguration mithilfe der HTTP-API ändern.
- Sie können mehrere Instanzen einer Anwendung gleichzeitig mit unterschiedlichen Konfigurationen und Sprachversionen ausführen.
- keine Notwendigkeit, den Speicher im Auge zu behalten;
- Sie müssen nichts an der Anwendung ändern.
Nachteile:
- PHP muss Variablen für jede Anforderung initialisieren.
Um Umgebungsvariablen aus der Konfigurationsdatei der nginx-Einheit zu übergeben, müssen Sie die Datei php.ini korrigieren:
; Nginx Unit variables_order=E
Befehl zum Starten der Anwendung mit Docker-Compose:
cd docker/nginx-unit && docker-compose up -d
Bibliothek zur Veranstaltungsprogrammierung. Es ist in PHP geschrieben.
Vorteile:
- Mithilfe der Bibliothek können Sie einen Server schreiben, der die Variablen nur einmal initialisiert und mit ihnen weiterarbeitet.
Nachteile:
- Sie müssen Code für den Server schreiben.
- müssen den Speicher verfolgen.
Wenn Sie das Flag --reboot-kernel-after-request für den Worker verwenden, wird der Symfony-Kernel für jede Anforderung neu initialisiert. Bei diesem Ansatz müssen Sie den Speicher nicht überwachen.
Befehl zum Starten der Anwendung mit Docker-Compose:
cd docker/react-php && docker-compose up -d --scale php=2
Webserver und PHP-Prozessmanager. Geschrieben in Golang.
Vorteile:
- Sie können einen Worker schreiben, der die Variablen nur einmal initialisiert und mit ihnen weiterarbeitet.
Nachteile:
- Sie müssen den Code für den Arbeiter schreiben.
- müssen den Speicher verfolgen.
Wenn Sie das Flag --reboot-kernel-after-request für den Worker verwenden, wird der Symfony-Kernel für jede Anforderung neu initialisiert. Bei diesem Ansatz müssen Sie den Speicher nicht überwachen.
Befehl zum Starten der Anwendung mit Docker-Compose:
cd docker/road-runner && docker-compose up -d
Testen
Die Tests wurden mit Yandex Tank durchgeführt.
Die Anwendung und Yandex Tank befanden sich auf verschiedenen virtuellen Servern.
Funktionen des virtuellen Servers mit der Anwendung:
Virtualisierung : KVM
CPU : 2 Kerne
RAM : 4096 MB
SSD : 50 GB
Verbindung : 100 MBit
Betriebssystem : CentOS 7 (64x)
Getestete Dienste:
- PHP-Fpm
- PHP-ppm
- Nginx-Einheit
- Straßenläufer
- Road-Runner-Neustart (mit dem Flag --reboot-kernel-after-request )
- reagieren-php
- React -PHP-Reboot (mit dem Flag --reboot-kernel-after-request )
Für Tests mit 1000/10000 U / min wurde der Dienst php-fpm-80 hinzugefügt
Die php-fpm Konfiguration wurde dafür verwendet:
pm = dynamic pm.max_children = 80
Yandex Tank bestimmt im Voraus, wie oft auf das Ziel geschossen werden muss, und stoppt erst, wenn die Patronen aufgebraucht sind. Abhängig von der Antwortgeschwindigkeit des Dienstes kann die Testzeit länger sein als in der Testkonfiguration angegeben. Aus diesem Grund können Grafiken verschiedener Dienste unterschiedliche Längen haben. Je langsamer der Dienst reagiert, desto länger ist sein Zeitplan.
Für jeden Service und jede Konfiguration des Yandex-Tanks wurde nur ein Test durchgeführt. Aus diesem Grund können Zahlen ungenau sein. Es war wichtig, die Merkmale der Dienstleistungen im Verhältnis zueinander zu bewerten.
100 rps
Phantom Yandex Tank Konfiguration
phantom: load_profile: load_type: rps schedule: line(1, 100, 60s) const(100, 540s)
Detaillierte Berichtslinks
Perzentile der Antwortzeit
Überwachung
Grafiken

Grafik 1.1 Durchschnittliche Antwortzeit pro Sekunde

Grafik 1.2 Durchschnittliche Prozessorlast pro Sekunde

Grafik 1.3 Durchschnittlicher Speicherverbrauch pro Sekunde
500 rps
Phantom Yandex Tank Konfiguration
phantom: load_profile: load_type: rps schedule: line(1, 500, 60s) const(500, 540s)
Detaillierte Berichtslinks
Perzentile der Antwortzeit
Überwachung
Grafiken

Grafik 2.1 Durchschnittliche Antwortzeit pro Sekunde

Grafik 2.2 Durchschnittliche Prozessorlast pro Sekunde

Grafik 2.3 Durchschnittlicher Speicherverbrauch pro Sekunde
1000 rps
Phantom Yandex Tank Konfiguration
phantom: load_profile: load_type: rps schedule: line(1, 1000, 60s) const(1000, 60s)
Detaillierte Berichtslinks
Perzentile der Antwortzeit
Überwachung
Grafiken

Grafik 3.1 Durchschnittliche Antwortzeit pro Sekunde

Grafik 3.2 Durchschnittliche Antwortzeit pro Sekunde (ohne PHP-Fpm, PHP-ppm, Roadrunner-Neustart)

Grafik 3.3 Durchschnittliche Prozessorlast pro Sekunde

Grafik 3.4 Durchschnittlicher Speicherverbrauch pro Sekunde
10000 U / min
Phantom Yandex Tank Konfiguration
phantom: load_profile: load_type: rps schedule: line(1, 10000, 30s) const(10000, 30s)
Detaillierte Berichtslinks
Perzentile der Antwortzeit
Überwachung

Grafik 4.1 Durchschnittliche Reaktionszeit pro Sekunde

Grafik 4.2 Durchschnittliche Reaktionszeit pro Sekunde (ohne php-fpm, php-ppm)

Grafik 4.3 Durchschnittliche Prozessorlast pro Sekunde

Grafik 4.4 Durchschnittlicher Speicherverbrauch pro Sekunde
Zusammenfassung
Hier sind Diagramme, die die Änderung der Eigenschaften von Diensten in Abhängigkeit von der Last zeigen. Beim Anzeigen von Diagrammen ist zu berücksichtigen, dass nicht alle Dienste 100% der Anfragen beantwortet haben.

Grafik 5.1 95% -Perzentil der Antwortzeit

Grafik 5.2 95% Perzentil der Antwortzeit (ohne PHP-Fpm)

Grafik 5.3 Maximale CPU-Auslastung

Tabelle 5.4 Maximaler Speicherverbrauch
Die optimale Lösung (ohne den Code zu ändern) ist meiner Meinung nach der Prozessmanager der Nginx-Einheit. Es zeigt gute Ergebnisse in der Reaktionsgeschwindigkeit und wird vom Unternehmen unterstützt.
In jedem Fall müssen der Entwicklungsansatz und die Tools individuell ausgewählt werden, abhängig von Ihren Workloads, Serverressourcen und Entwicklerfähigkeiten.
UPD
Für Tests mit 1000/10000 U / min wurde der Dienst php-fpm-80 hinzugefügt
Die php-fpm Konfiguration wurde dafür verwendet:
pm = dynamic pm.max_children = 80