
O teste foi realizado usando o Yandex Tank.
Symfony 4 e PHP 7.2 foram usados como uma aplicação.
O objetivo era comparar as características dos serviços em diferentes cargas e encontrar a melhor opção.
Por conveniência, tudo é coletado em contêineres do docker e gerado usando o docker-compose.
Sob um gato, existem muitas tabelas e gráficos.
O código fonte está aqui .
Todos os exemplos de comandos descritos no artigo devem ser executados no diretório do projeto.
App
O aplicativo é executado no Symfony 4 e PHP 7.2.
Responde apenas uma rota e retorna:
- número aleatório;
- meio ambiente
- pid do processo;
- nome do serviço com o qual trabalha;
- variáveis php.ini.
Exemplo de resposta:
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": "" } }
O PHP está configurado em cada contêiner:
- OPcache está ativado;
- cache de bootstrap configurado usando o compositor;
- As configurações do php.ini estão alinhadas com as melhores práticas do Symfony .
Os logs são escritos em stderr:
/config/packages/prod/monolog.yaml
monolog: handlers: main: type: stream path: "php://stderr" level: error console: type: console
O cache é gravado em / 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-compor lança três contêineres principais:
- Nginx - servidor proxy reverso;
- Código de aplicativo preparado por aplicativo com todas as dependências;
- PHP FPM \ Unidade Nginx \ Road Runner \ React PHP - servidor de aplicativos.
O processamento de solicitações é limitado a duas instâncias do aplicativo (pelo número de núcleos do processador).
Serviços
Gerenciador de processos PHP. Escrito em C.
Prós:
- não há necessidade de acompanhar a memória;
- não há necessidade de alterar nada no aplicativo.
Contras:
- O PHP deve inicializar variáveis para cada solicitação.
Comando para iniciar o aplicativo com docker-compose:
cd docker/php-fpm && docker-compose up -d
Gerenciador de processos PHP. Está escrito em PHP.
Prós:
- inicializa as variáveis uma vez e depois as usa;
- não é necessário alterar nada no aplicativo (existem módulos prontos para Symfony / Laravel, Zend, CakePHP).
Contras:
- precisa seguir a memória.
Comando para iniciar o aplicativo com docker-compose:
cd docker/php-ppm && docker-compose up -d
Servidor de aplicativos da equipe Nginx. Escrito em C.
Prós:
- Você pode alterar a configuração usando a API HTTP;
- Você pode executar várias instâncias de um aplicativo simultaneamente com diferentes configurações e versões de idiomas;
- não há necessidade de acompanhar a memória;
- não há necessidade de alterar nada no aplicativo.
Contras:
- O PHP deve inicializar variáveis para cada solicitação.
Para passar variáveis de ambiente do arquivo de configuração da unidade nginx, você precisa corrigir o php.ini:
; Nginx Unit variables_order=E
Comando para iniciar o aplicativo com docker-compose:
cd docker/nginx-unit && docker-compose up -d
Biblioteca para programação de eventos. Está escrito em PHP.
Prós:
- usando a biblioteca, você pode escrever um servidor que inicializará as variáveis apenas uma vez e continuará a trabalhar com elas.
Contras:
- você deve escrever o código para o servidor;
- precisa acompanhar a memória.
Se você usar o sinalizador --reboot-kernel-after-request para o trabalhador, o Symfony Kernel será reinicializado para cada solicitação. Com essa abordagem, você não precisa monitorar a memória.
Comando para iniciar o aplicativo com docker-compose:
cd docker/react-php && docker-compose up -d --scale php=2
Servidor Web e gerenciador de processos PHP. Escrito em Golang.
Prós:
- você pode escrever um trabalhador que inicializará as variáveis apenas uma vez e continuará trabalhando com elas.
Contras:
- você deve escrever o código para o trabalhador;
- precisa acompanhar a memória.
Se você usar o sinalizador --reboot-kernel-after-request para o trabalhador, o Symfony Kernel será reinicializado para cada solicitação. Com essa abordagem, você não precisa monitorar a memória.
Comando para iniciar o aplicativo com docker-compose:
cd docker/road-runner && docker-compose up -d
Teste
O teste foi realizado usando o Yandex Tank.
O aplicativo e o Yandex Tank estavam em diferentes servidores virtuais.
Recursos do servidor virtual com o aplicativo:
Virtualização : KVM
CPU : 2 núcleos
RAM : 4096 MB
SSD : 50 GB
Conexão : 100MBit
SO : CentOS 7 (64x)
Serviços testados:
- php-fpm
- php-ppm
- nginx-unit
- corredor da estrada
- road-runner-reboot (com o sinalizador --reboot-kernel-after-request )
- react-php
- react-php-reboot (com o sinalizador --reboot-kernel-after-request )
Para testes 1000/10000 rps, serviço php-fpm-80 adicionado
A configuração php-fpm foi usada para isso:
pm = dynamic pm.max_children = 80
O Yandex Tank determina antecipadamente quantas vezes ele precisa atirar no alvo e não pára até que os cartuchos acabem. Dependendo da velocidade da resposta do serviço, o tempo de teste pode ser maior que o especificado na configuração de teste. Por esse motivo, os gráficos de diferentes serviços podem ter comprimentos diferentes. Quanto mais lento o serviço responder, maior será o cronograma.
Para cada serviço e configuração do Yandex Tank, apenas um teste foi realizado. Por esse motivo, os números podem ser imprecisos. Era importante avaliar as características dos serviços em relação um ao outro.
100 rps
Configuração do tanque fantasma Yandex
phantom: load_profile: load_type: rps schedule: line(1, 100, 60s) const(100, 540s)
Links detalhados do relatório
Percentis de tempo de resposta
Monitoramento
Gráficos

Gráfico 1.1 Tempo médio de resposta por segundo

Gráfico 1.2 Carga média do processador por segundo

Gráfico 1.3 Consumo médio de memória por segundo
500 rps
Configuração do tanque fantasma Yandex
phantom: load_profile: load_type: rps schedule: line(1, 500, 60s) const(500, 540s)
Links detalhados do relatório
Percentis de tempo de resposta
Monitoramento
Gráficos

Gráfico 2.1 Tempo médio de resposta por segundo

Gráfico 2.2 Carga média do processador por segundo

Gráfico 2.3 Consumo médio de memória por segundo
1000 rps
Configuração do tanque fantasma Yandex
phantom: load_profile: load_type: rps schedule: line(1, 1000, 60s) const(1000, 60s)
Links detalhados do relatório
Percentis de tempo de resposta
Monitoramento
Gráficos

Gráfico 3.1 Tempo médio de resposta por segundo

Gráfico 3.2 Tempo médio de resposta por segundo (sem php-fpm, php-ppm, road-runner-reboot)

Gráfico 3.3 Carga média do processador por segundo

Gráfico 3.4 Consumo médio de memória por segundo
10000 rps
Configuração do tanque fantasma Yandex
phantom: load_profile: load_type: rps schedule: line(1, 10000, 30s) const(10000, 30s)
Links detalhados do relatório
Percentis de tempo de resposta
Monitoramento

Gráfico 4.1 Tempo médio de resposta por segundo

Gráfico 4.2 Tempo médio de resposta por segundo (sem php-fpm, php-ppm)

Gráfico 4.3 Carga média do processador por segundo

Gráfico 4.4 Consumo médio de memória por segundo
Sumário
Aqui estão gráficos que mostram a alteração nas características dos serviços, dependendo da carga. Ao visualizar gráficos, vale considerar que nem todos os serviços responderam 100% das solicitações.

Gráfico 5.1 Percentil 95% do tempo de resposta

Gráfico 5.2 Percentil 95% do tempo de resposta (sem php-fpm)

Gráfico 5.3 Carga máxima da CPU

Gráfico 5.4 Consumo máximo de memória
A solução ideal (sem alterar o código), na minha opinião, é o gerente de processos da Unidade Nginx. Apresenta bons resultados em velocidade de resposta e conta com o apoio da empresa.
De qualquer forma, a abordagem e as ferramentas de desenvolvimento precisam ser selecionadas individualmente, dependendo das cargas de trabalho, recursos do servidor e recursos do desenvolvedor.
UPD
Para testes 1000/10000 rps, serviço php-fpm-80 adicionado
A configuração php-fpm foi usada para isso:
pm = dynamic pm.max_children = 80