
Les tests ont été effectués à l'aide de Yandex Tank.
Symfony 4 et PHP 7.2 ont été utilisés comme application.
L'objectif était de comparer les caractéristiques des services à différentes charges et de trouver la meilleure option.
Pour plus de commodité, tout est collecté dans des conteneurs Docker et est élevé à l'aide de Docker-compose.
Sous un chat, il y a beaucoup de tableaux et de graphiques.
Le code source est ici .
Tous les exemples de commandes décrits dans l'article doivent être exécutés à partir du répertoire du projet.
App
L'application fonctionne sur Symfony 4 et PHP 7.2.
Répond à un seul itinéraire et retourne:
- nombre aléatoire;
- l'environnement
- pid du processus;
- nom du service avec lequel il travaille;
- Variables php.ini.
Exemple de réponse:
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 est configuré dans chaque conteneur:
Les journaux sont écrits dans stderr:
/config/packages/prod/monolog.yaml
monolog: handlers: main: type: stream path: "php://stderr" level: error console: type: console
Le cache est écrit dans / dev / shm:
/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; } } } ...
Chaque docker-compose lance trois conteneurs principaux:
- Nginx - serveur proxy inverse;
- App - code d'application préparé avec toutes les dépendances;
- PHP FPM \ Nginx Unit \ Road Runner \ React PHP - serveur d'applications.
Le traitement des demandes est limité à deux instances d'application (par le nombre de cœurs de processeur).
Les services
Gestionnaire de processus PHP. Écrit en C.
Avantages:
- pas besoin de garder une trace de la mémoire;
- pas besoin de changer quoi que ce soit dans l'application.
Inconvénients:
- PHP doit initialiser les variables pour chaque requête.
Commande pour lancer l'application avec docker-compose:
cd docker/php-fpm && docker-compose up -d
Gestionnaire de processus PHP. Il est écrit en PHP.
Avantages:
- initialise les variables une fois puis les utilise;
- pas besoin de changer quoi que ce soit dans l'application (il existe des modules prêts à l'emploi pour Symfony / Laravel, Zend, CakePHP).
Inconvénients:
- besoin de suivre la mémoire.
Commande pour lancer l'application avec docker-compose:
cd docker/php-ppm && docker-compose up -d
Serveur d'applications de l'équipe Nginx. Écrit en C.
Avantages:
- Vous pouvez modifier la configuration à l'aide de l'API HTTP;
- Vous pouvez exécuter plusieurs instances d'une même application simultanément avec différentes configurations et versions linguistiques;
- pas besoin de garder une trace de la mémoire;
- pas besoin de changer quoi que ce soit dans l'application.
Inconvénients:
- PHP doit initialiser les variables pour chaque requête.
Pour transmettre les variables d'environnement à partir du fichier de configuration de l'unité nginx, vous devez corriger php.ini:
; Nginx Unit variables_order=E
Commande pour lancer l'application avec docker-compose:
cd docker/nginx-unit && docker-compose up -d
Bibliothèque pour la programmation d'événements. Il est écrit en PHP.
Avantages:
- en utilisant la bibliothèque, vous pouvez écrire un serveur qui initialisera les variables une seule fois et continuera à travailler avec elles.
Inconvénients:
- vous devez écrire du code pour le serveur;
- besoin de garder une trace de la mémoire.
Si vous utilisez l' indicateur --reboot-kernel-after-request pour le travailleur, Symfony Kernel sera réinitialisé pour chaque demande. Avec cette approche, vous n'avez pas besoin de surveiller la mémoire.
Commande pour lancer l'application avec docker-compose:
cd docker/react-php && docker-compose up -d --scale php=2
Serveur Web et gestionnaire de processus PHP. Écrit à Golang.
Avantages:
- vous pouvez écrire un travailleur qui initialisera les variables une seule fois et continuera à travailler avec elles.
Inconvénients:
- vous devez écrire le code du travailleur;
- besoin de garder une trace de la mémoire.
Si vous utilisez l' indicateur --reboot-kernel-after-request pour le travailleur, Symfony Kernel sera réinitialisé pour chaque demande. Avec cette approche, vous n'avez pas besoin de surveiller la mémoire.
Commande pour lancer l'application avec docker-compose:
cd docker/road-runner && docker-compose up -d
Test
Les tests ont été effectués à l'aide de Yandex Tank.
L'application et Yandex Tank étaient sur différents serveurs virtuels.
Caractéristiques du serveur virtuel avec l'application:
Virtualisation : KVM
CPU : 2 cœurs
Mémoire RAM : 4096 Mo
SSD : 50 Go
Connexion : 100 Mo
Système d' exploitation : CentOS 7 (64x)
Services testés:
- php-fpm
- php-ppm
- unité nginx
- coureur
- road-runner-reboot (avec le drapeau --reboot-kernel-after-request )
- react-php
- react-php-reboot (avec le drapeau --reboot-kernel-after-request )
Pour les tests 1000/10000 rps, ajout du service php-fpm-80
La configuration php-fpm a été utilisée pour cela:
pm = dynamic pm.max_children = 80
Yandex Tank détermine à l'avance le nombre de fois qu'il a besoin de tirer sur la cible et ne s'arrête que lorsque les cartouches sont épuisées. Selon la vitesse de réponse du service, le temps de test peut être plus long que celui spécifié dans la configuration de test. Pour cette raison, les graphiques de différents services peuvent avoir des longueurs différentes. Plus le service répond lentement, plus son calendrier est long.
Pour chaque service et configuration de Yandex Tank, un seul essai a été effectué. Pour cette raison, les chiffres peuvent être inexacts. Il était important d'évaluer les caractéristiques des services les uns par rapport aux autres.
100 rps
Configuration du réservoir Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 100, 60s) const(100, 540s)
Liens vers les rapports détaillés
Centiles de temps de réponse
Suivi
Graphiques

Graphique 1.1 Temps de réponse moyen par seconde

Graphique 1.2 Charge moyenne du processeur par seconde

Graphique 1.3 Consommation moyenne de mémoire par seconde
500 rps
Configuration du réservoir Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 500, 60s) const(500, 540s)
Liens vers les rapports détaillés
Centiles de temps de réponse
Suivi
Graphiques

Graphique 2.1 Temps de réponse moyen par seconde

Graphique 2.2 Charge moyenne du processeur par seconde

Graphique 2.3 Consommation moyenne de mémoire par seconde
1000 rps
Configuration du réservoir Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 1000, 60s) const(1000, 60s)
Liens vers les rapports détaillés
Centiles de temps de réponse
Suivi
Graphiques

Graphique 3.1 Temps de réponse moyen par seconde

Graphique 3.2 Temps de réponse moyen par seconde (sans php-fpm, php-ppm, road-runner-reboot)

Graphique 3.3 Charge moyenne du processeur par seconde

Graphique 3.4 Consommation moyenne de mémoire par seconde
10000 rps
Configuration du réservoir Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 10000, 30s) const(10000, 30s)
Liens vers les rapports détaillés
Centiles de temps de réponse
Suivi

Graphique 4.1 Temps de réponse moyen par seconde

Graphique 4.2 Temps de réponse moyen par seconde (sans php-fpm, php-ppm)

Graphique 4.3 Charge moyenne du processeur par seconde

Graphique 4.4 Consommation moyenne de mémoire par seconde
Résumé
Voici des graphiques montrant l'évolution des caractéristiques des services en fonction de la charge. Lors de la visualisation des graphiques, il convient de noter que tous les services n'ont pas répondu à 100% des demandes.

Graphique 5.1 95% de centile du temps de réponse

Graphique 5.2 Centile à 95% du temps de réponse (sans php-fpm)

Graphique 5.3 Charge CPU maximale

Graphique 5.4 Consommation maximale de mémoire
La solution optimale (sans changer le code), à mon avis, est le gestionnaire de processus Nginx Unit. Il montre de bons résultats en termes de vitesse de réponse et bénéficie du soutien de l'entreprise.
Dans tous les cas, l'approche et les outils de développement doivent être sélectionnés individuellement, en fonction de vos charges de travail, des ressources du serveur et des capacités des développeurs.
UPD
Pour les tests 1000/10000 rps, ajout du service php-fpm-80
La configuration php-fpm a été utilisée pour cela:
pm = dynamic pm.max_children = 80