
Antes de dimensionar e dimensionar sua infraestrutura, a primeira etapa é garantir que os recursos estejam sendo usados corretamente e que a configuração do aplicativo não prejudique seu desempenho. O principal objetivo da equipe de engenharia é garantir a operação contínua e ininterrupta de qualquer sistema projetado e implantado com recursos mínimos.
Enfrentamos o problema acima, em que nosso sistema implantado era usado diariamente por um milhão de usuários que se conectavam em bursts de vez em quando. Isso significa que implantar vários servidores ou dimensioná-los não será a melhor solução nessa situação.
Este artigo é sobre como ajustar o Nginx para melhorar o desempenho, ou seja, aumentar o RPS (Solicitações por segundo) na API HTTP. Tentei falar sobre a otimização que aplicamos no sistema implantado para processar dezenas de milhares de solicitações por segundo sem desperdiçar uma grande quantidade de recursos.
Plano de ação: você precisa executar a API HTTP (escrita em Python usando flask), proxy com Nginx; alta largura de banda é necessária. O conteúdo da API mudará em intervalos de um dia.
otimização do processo
nominal
de obtenção do melhor resultado; o uso mais eficiente de uma situação ou recurso.
Usamos o supervisor para iniciar o servidor WSGI com as seguintes configurações:
- Gunicorn com trabalhadores Meinheld
- Número de trabalhadores: número de CPUs * 2 + 1
- Vincule o soquete a um endereço Unix em vez de um IP, isso aumentará ligeiramente a velocidade .
O comando do supervisor se parece com isto:
gunicorn api:app --workers=5 --worker-
class=meinheld.gmeinheld.MeinheldWorker --bind=unix:api.sock
Tentamos otimizar a configuração do Nginx e verificamos o que funcionou melhor para nós.
Para avaliar o desempenho da API, usamos wrk com o seguinte comando:
wrk -t20 -c200 -d20s http://api.endpoint/resource
Configuração padrão
Primeiro, executamos o teste de carga da API sem nenhuma alteração e obtivemos as seguintes estatísticas:
Running 20s test @ http://api.endpoint/resource
20 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 192.48ms 274.78ms 1.97s 87.18%
Req/Sec 85.57 29.20 202.00 72.83%
33329 requests in 20.03s, 29.59MB read
Socket errors: connect 0, read 0, write 0, timeout 85
Requests/sec: 1663.71
Transfer/sec: 1.48MB
Atualizando a configuração padrão
Vamos atualizar a configuração padrão do Nginx, ou seja, nginx.conf em /etc/nginx/nginx.conf
worker_processes auto;
#or should be equal to the CPU core, you can use `grep processor /proc/cpuinfo | wc -l` to find; auto does it implicitly.
worker_connections 1024;
# default is 768; find optimum value for your server by `ulimit -n`
access_log off;
# to boost I/O on HDD we can disable access logs
# this prevent nginx from logging every action in a log file named `access.log`.
keepalive_timeout 15;
# default is 65;
# server will close connection after this time (in seconds)
gzip_vary on;
gzip_proxied any;
gzip_comp_level 2;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# reduces the data that needs to be sent over the networknginx.conf (/etc/nginx/nginx.conf)
Após as alterações, executamos a verificação de configuração:
sudo nginx -t
Se a verificação for bem-sucedida, você pode reiniciar o Nginx para refletir as alterações:
sudo service nginx restart
Com essa configuração, realizamos o teste de carga da API e obtivemos o seguinte resultado:
Running 20s test @ http://api.endpoint/resource
20 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 145.80ms 237.97ms 1.95s 89.51%
Req/Sec 107.99 41.34 202.00 66.09%
42898 requests in 20.03s, 39.03MB read
Socket errors: connect 0, read 0, write 0, timeout 46
Non-2xx or 3xx responses: 2
Requests/sec: 2141.48
Transfer/sec: 1.95MB
Essas configurações reduziram os tempos limites e aumentaram o RPS (solicitações por segundo), mas não muito.
Adicionando Cache Nginx
Como, em nosso caso, o conteúdo do endpoint será atualizado em um intervalo de um dia, isso cria um ambiente adequado para o armazenamento em cache das respostas da API.
Mas adicionar cache o torna inválido ... esta é uma das duas dificuldades aqui.
Na ciência da computação, existem apenas duas complicações: invalidar o cache e nomear coisas. - Phil Carlton
Escolhemos uma solução simples para limpar o diretório de cache usando um cronjob depois de atualizar o conteúdo no sistema downstream.
Em seguida, o Nginx fará todo o trabalho difícil, mas agora precisamos ter certeza de que o Nginx está 100% pronto!
Para adicionar cache ao Nginx, você precisa adicionar várias diretivas ao arquivo de configuração do Nginx.
Antes disso, precisamos criar um diretório para armazenar os dados do cache:
sudo mkdir -p /data/nginx/cache
Mudanças na configuração do Nginx:
proxy_cache_path /data/nginx/cache keys_zone=my_zone:10m inactive=1d;
server {
...
location /api-endpoint/ {
proxy_cache my_zone;
proxy_cache_key "$host$request_uri$http_authorization";
proxy_cache_valid 404 302 1m;
proxy_cache_valid 200 1d;
add_header X-Cache-Status $upstream_cache_status;
}
...
}
Solicitações por proxy de cache (configuração Nginx)
Após essa alteração na configuração, testamos o carregamento da API e obtivemos o seguinte resultado:
Running 20s test @ http://api.endpoint/resource
20 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 6.88ms 5.44ms 88.91ms 81.36%
Req/Sec 1.59k 500.04 2.95k 62.50%
634405 requests in 20.06s, 589.86MB read
Requests/sec: 31624.93
Transfer/sec: 29.40MB
Assim, obtivemos um aumento de quase 19x no desempenho ao adicionar o cache.
Observação de um especialista do Timeweb :
É importante lembrar que as consultas de cache que gravam no banco de dados resultarão em uma resposta em cache, mas nenhuma gravação no banco de dados.
Cache Nginx em RAM (memória de acesso aleatório)
Vamos dar um passo adiante! Atualmente, nossos dados de cache são armazenados em disco. E se salvarmos esses dados na RAM? Em nosso caso, os dados de resposta são limitados e não grandes.
Portanto, primeiro você precisa criar um diretório onde o cache de RAM será montado:
sudo mkdir -p /data/nginx/ramcache
Para montar o diretório criado na RAM usando tmpfs , use o comando:
sudo mount -t tmpfs -o size=256M tmpfs /data/nginx/ramcache
Isso monta / data / nginx / ramcache na RAM, alocando 256 MB.
Se você acha que deseja desativar o cache de RAM, basta executar o comando:
sudo umount /data/nginx/ramcache
Para recriar automaticamente o diretório de cache na RAM após a reinicialização, precisamos atualizar o arquivo / etc / fstab . Adicione a seguinte linha a ele:
tmpfs /data/nginx/ramcache tmpfs defaults,size=256M 0 0
Observação: também temos que registrar o valor proxy_cache_path com o caminho para ramcache ( / data / nginx / ramcache ).
Depois de atualizar a configuração, realizamos novamente o teste de carga da API e obtivemos o seguinte resultado:
Running 20s test @ http://api.endpoint/resource
20 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.57ms 5.69ms 277.76ms 92.94%
Req/Sec 1.98k 403.94 4.55k 71.77%
789306 requests in 20.04s, 733.89MB read
Requests/sec: 39387.13
Transfer/sec: 36.62MB
O armazenamento do cache na RAM resultou em uma melhoria significativa de quase 23 vezes .
Registro de acesso em buffer
Mantemos um registro de acesso aos aplicativos com proxy, mas você pode primeiro salvar o registro em um buffer e só depois gravá-lo no disco:
- se a próxima linha do log não couber no buffer
- se os dados no buffer forem mais antigos do que o especificado no parâmetro flush .
Este procedimento reduzirá a frequência de gravação realizada com cada solicitação. Para fazer isso, precisamos apenas adicionar os parâmetros buffer e flush com o valor apropriado na diretiva access_log :
location / {
...
access_log /var/log/nginx/fast_api.log combined buffer=256k flush=10s;
error_log /var/log/nginx/fast_api.err.log;
}
Registro do buffer antes de ser gravado no disco Assim, de acordo com a configuração acima, inicialmente os registros de acesso serão armazenados em buffer e salvos no disco somente quando o buffer atingir 256 KB ou os dados do buffer tiverem mais de 10 segundos.
Nota: o nome é log_format combinado aqui .
Após repetidos testes de estresse, obtivemos o seguinte resultado:
Running 20s test @ http://api.endpoint/resource
20 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.21ms 3.19ms 84.83ms 83.84%
Req/Sec 2.53k 379.87 6.02k 77.05%
1009771 requests in 20.03s, 849.31MB read
Requests/sec: 50413.44
Transfer/sec: 42.40MB
Essa configuração aumentou significativamente o número de solicitações por segundo, cerca de 30 vezes em comparação com o estágio inicial.
Resultado
Neste artigo, discutimos o processo de otimização da configuração do Nginx para melhorar o desempenho do RPS. O RPS foi aumentado de 1663 para ~ 50413 ( um aumento de cerca de 30 vezes ), o que fornece alto rendimento. Ajustando as configurações padrão, você pode melhorar o desempenho do sistema.
Vamos terminar o artigo com uma citação:
Faça funcionar primeiro. Então faça certo. Em seguida, otimize. - Kent Beck