
Las pruebas se realizaron con Yandex Tank.
Symfony 4 y PHP 7.2 se utilizaron como una aplicación.
El objetivo era comparar las características de los servicios en diferentes cargas y encontrar la mejor opción.
Por conveniencia, todo se recolecta en contenedores acoplables y se eleva utilizando docker-compose.
Debajo de un gato hay muchas tablas y gráficos.
El código fuente está aquí .
Todos los ejemplos de comandos descritos en el artículo deben ejecutarse desde el directorio del proyecto.
App
La aplicación se ejecuta en Symfony 4 y PHP 7.2.
Responde solo una ruta y regresa:
- número aleatorio
- medio ambiente
- pid del proceso;
- nombre del servicio con el que trabaja;
- php.ini variables.
Ejemplo de respuesta:
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á configurado en cada contenedor:
- OPcache está habilitado;
- caché de arranque configurado usando el compositor;
- La configuración de php.ini está en línea con las mejores prácticas de Symfony .
Los registros están escritos en stderr:
/config/packages/prod/monolog.yaml
monolog: handlers: main: type: stream path: "php://stderr" level: error console: type: console
El caché está escrito en / 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; } } } ...
Cada docker-compose lanza tres contenedores principales:
- Nginx - servidor proxy inverso;
- Aplicación: código de aplicación preparado con todas las dependencias;
- PHP FPM \ Nginx Unit \ Road Runner \ React PHP - servidor de aplicaciones.
El procesamiento de solicitudes está limitado a dos instancias de aplicación (por el número de núcleos de procesador).
Servicios
Administrador de procesos PHP. Escrito en C.
Pros:
- no es necesario hacer un seguimiento de la memoria;
- No es necesario cambiar nada en la aplicación.
Contras:
- PHP debe inicializar variables para cada solicitud.
Comando para iniciar la aplicación con docker-compose:
cd docker/php-fpm && docker-compose up -d
Administrador de procesos PHP. Está escrito en PHP.
Pros:
- inicializa las variables una vez y luego las usa;
- no es necesario cambiar nada en la aplicación (hay módulos listos para Symfony / Laravel, Zend, CakePHP).
Contras:
- Necesito seguir la memoria.
Comando para iniciar la aplicación con docker-compose:
cd docker/php-ppm && docker-compose up -d
Servidor de aplicaciones del equipo Nginx. Escrito en C.
Pros:
- Puede cambiar la configuración utilizando la API HTTP;
- Puede ejecutar varias instancias de una aplicación simultáneamente con diferentes configuraciones y versiones de idioma;
- no es necesario hacer un seguimiento de la memoria;
- No es necesario cambiar nada en la aplicación.
Contras:
- PHP debe inicializar variables para cada solicitud.
Para pasar variables de entorno desde el archivo de configuración de la unidad nginx, debe corregir php.ini:
; Nginx Unit variables_order=E
Comando para iniciar la aplicación con docker-compose:
cd docker/nginx-unit && docker-compose up -d
Biblioteca para programación de eventos. Está escrito en PHP.
Pros:
- utilizando la biblioteca, puede escribir un servidor que inicializará las variables solo una vez y continuará trabajando con ellas.
Contras:
- debes escribir código para el servidor;
- necesita hacer un seguimiento de la memoria.
Si utiliza el indicador --reboot-kernel-after-request para el trabajador, Symfony Kernel se reiniciará para cada solicitud. Con este enfoque, no necesita monitorear la memoria.
Comando para iniciar la aplicación con docker-compose:
cd docker/react-php && docker-compose up -d --scale php=2
Servidor web y gestor de procesos PHP. Escrito en Golang.
Pros:
- puede escribir un trabajador que inicializará las variables solo una vez y continuará trabajando con ellas.
Contras:
- debe escribir el código para el trabajador;
- necesita hacer un seguimiento de la memoria.
Si utiliza el indicador --reboot-kernel-after-request para el trabajador, Symfony Kernel se reiniciará para cada solicitud. Con este enfoque, no necesita monitorear la memoria.
Comando para iniciar la aplicación con docker-compose:
cd docker/road-runner && docker-compose up -d
Prueba
Las pruebas se realizaron con Yandex Tank.
La aplicación y Yandex Tank estaban en diferentes servidores virtuales.
Características del servidor virtual con la aplicación:
Virtualización : KVM
CPU : 2 núcleos
RAM : 4096 MB
SSD : 50 GB
Conexión : 100 MBit
SO : CentOS 7 (64x)
Servicios probados:
- php-fpm
- php-ppm
- unidad nginx
- correcaminos
- road-runner-reboot (con la bandera --reboot-kernel-after-request )
- reaccionar-php
- react-php-reboot (con el indicador --reboot-kernel-after-request )
Para pruebas 1000/10000 rps servicio php-fpm-80 agregado
Se usó la configuración php-fpm para ello:
pm = dynamic pm.max_children = 80
Yandex Tank determina de antemano cuántas veces necesita disparar al objetivo, y no se detiene hasta que se agoten los cartuchos. Dependiendo de la velocidad de respuesta del servicio, el tiempo de prueba puede ser mayor que el especificado en la configuración de prueba. Debido a esto, los gráficos de diferentes servicios pueden tener diferentes longitudes. Cuanto más lento responda el servicio, más largo será su horario.
Para cada servicio y configuración de Yandex Tank, solo se realizó una prueba. Debido a esto, los números pueden ser inexactos. Era importante evaluar las características de los servicios entre sí.
100 rps
Configuración del tanque Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 100, 60s) const(100, 540s)
Enlaces de informes detallados
Percentiles de tiempo de respuesta
Monitoreo
Gráficos

Gráfico 1.1 Tiempo de respuesta promedio por segundo

Gráfico 1.2 Carga promedio del procesador por segundo

Gráfico 1.3 Consumo promedio de memoria por segundo
500 rps
Configuración del tanque Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 500, 60s) const(500, 540s)
Enlaces de informes detallados
Percentiles de tiempo de respuesta
Monitoreo
Gráficos

Gráfico 2.1 Tiempo de respuesta promedio por segundo

Gráfico 2.2 Carga promedio del procesador por segundo

Gráfico 2.3 Consumo promedio de memoria por segundo
1000 rps
Configuración del tanque Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 1000, 60s) const(1000, 60s)
Enlaces de informes detallados
Percentiles de tiempo de respuesta
Monitoreo
Gráficos

Gráfico 3.1 Tiempo de respuesta promedio por segundo

Gráfico 3.2 Tiempo de respuesta promedio por segundo (sin php-fpm, php-ppm, road-runner-reinicio)

Gráfico 3.3 Carga promedio del procesador por segundo

Gráfico 3.4 Consumo promedio de memoria por segundo
10000 rps
Configuración del tanque Phantom Yandex
phantom: load_profile: load_type: rps schedule: line(1, 10000, 30s) const(10000, 30s)
Enlaces de informes detallados
Percentiles de tiempo de respuesta
Monitoreo

Gráfico 4.1 Tiempo de respuesta promedio por segundo

Gráfico 4.2 Tiempo de respuesta promedio por segundo (sin php-fpm, php-ppm)

Gráfico 4.3 Carga promedio del procesador por segundo

Gráfico 4.4 Consumo promedio de memoria por segundo
Resumen
Aquí hay gráficos que muestran el cambio en las características de los servicios dependiendo de la carga. Al ver los gráficos, vale la pena considerar que no todos los servicios respondieron el 100% de las solicitudes.

Gráfico 5.1 percentil 95% del tiempo de respuesta

Gráfico 5.2 percentil 95% del tiempo de respuesta (sin php-fpm)

Gráfico 5.3 Carga máxima de CPU

Gráfico 5.4 Consumo máximo de memoria
La solución óptima (sin cambiar el código), en mi opinión, es el administrador de procesos de la Unidad Nginx. Muestra buenos resultados en velocidad de respuesta y cuenta con el apoyo de la empresa.
En cualquier caso, el enfoque de desarrollo y las herramientas deben seleccionarse individualmente, dependiendo de sus cargas de trabajo, recursos del servidor y capacidades del desarrollador.
UPD
Para pruebas 1000/10000 rps servicio php-fpm-80 agregado
Se usó la configuración php-fpm para ello:
pm = dynamic pm.max_children = 80