BLOG | NGINX

Por dentro do NGINX: Como projetamos para desempenho e escala

Miniatura de Owen Garrett
Owen Garrett
Publicado em 10 de junho de 2015

O NGINX lidera o grupo em desempenho na web, e tudo isso se deve à maneira como o software foi projetado. Enquanto muitos servidores web e servidores de application usam uma arquitetura simples baseada em processos ou encadeada, o NGINX se destaca com uma arquitetura sofisticada orientada a eventos que permite que ele seja dimensionado para centenas de milhares de conexões simultâneas em hardware moderno.

O infográfico Inside NGINX analisa detalhadamente a arquitetura de processo de alto nível para ilustrar como o NGINX lida com múltiplas conexões dentro de um único processo. Este blog explica como tudo funciona com mais detalhes.

Definindo o cenário – O modelo de processo NGINX

Para entender melhor esse design, você precisa entender como o NGINX é executado. O NGINX tem um processo mestre (que executa operações privilegiadas, como leitura de configuração e vinculação a portas) e vários processos de trabalho e auxiliares.

# service nginx restart* Restarting nginx
# ps -ef --forest | grep nginx
root     32475     1  0 13:36 ?        00:00:00 nginx: master process /usr/sbin/nginx 
                                                -c /etc/nginx/nginx.conf
nginx    32476 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32477 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32479 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32480 32475  0 13:36 ?        00:00:00  _ nginx: worker process
nginx    32481 32475  0 13:36 ?        00:00:00  _ nginx: cache manager process
nginx    32482 32475  0 13:36 ?        00:00:00  _ nginx: cache loader process

Neste servidor de quatro núcleos, o processo mestre NGINX cria quatro processos de trabalho e alguns processos auxiliares de cache que gerenciam o cache de conteúdo no disco.

Por que a arquitetura é importante?

A base fundamental de qualquer application Unix é o thread ou processo. (Da perspectiva do sistema operacional Linux, threads e processos são praticamente idênticos; a principal diferença é o grau em que eles compartilham memória.) Um thread ou processo é um conjunto independente de instruções que o sistema operacional pode programar para execução em um núcleo de CPU. A maioria dos applications complexos executa vários threads ou processos em paralelo por dois motivos:

  • Eles podem usar mais núcleos de computação ao mesmo tempo.
  • Threads e processos tornam muito fácil realizar operações em paralelo (por exemplo, para manipular várias conexões ao mesmo tempo).

Processos e threads consomem recursos. Cada um deles usa memória e outros recursos do sistema operacional e precisa ser trocado entre os núcleos (uma operação chamada de troca de contexto ). A maioria dos servidores modernos pode lidar com centenas de pequenos threads ou processos ativos simultaneamente, mas o desempenho cai seriamente quando a memória se esgota ou quando uma alta carga de E/S causa um grande volume de trocas de contexto.

A maneira comum de projetar applications de rede é atribuir um thread ou processo a cada conexão. Essa arquitetura é simples e fácil de implementar, mas não é escalável quando o application precisa lidar com milhares de conexões simultâneas.

Como o NGINX funciona?

O NGINX usa um modelo de processo previsível que é ajustado aos recursos de hardware disponíveis:

  • O processo mestre executa operações privilegiadas, como leitura de configuração e vinculação a portas, e então cria um pequeno número de processos filho (os próximos três tipos).
  • O processo do carregador de cache é executado na inicialização para carregar o cache baseado em disco na memória e, em seguida, sai. Ele é programado de forma conservadora, portanto suas demandas de recursos são baixas.
  • O processo do gerenciador de cache é executado periodicamente e remove entradas dos caches de disco para mantê-los dentro dos tamanhos configurados.
  • Os processos de trabalho fazem todo o trabalho! Eles gerenciam conexões de rede, leem e gravam conteúdo no disco e se comunicam com servidores upstream.

A configuração NGINX recomendada na maioria dos casos – executando um processo de trabalho por núcleo de CPU – faz o uso mais eficiente dos recursos de hardware. Você o configura definindo o parâmetro auto na diretiva worker_processes :

worker_processes auto;

Quando um servidor NGINX está ativo, somente os processos de trabalho ficam ocupados. Cada processo de trabalho manipula múltiplas conexões de forma não bloqueante, reduzindo o número de trocas de contexto.

Cada processo de trabalho é de thread único e executado de forma independente, capturando novas conexões e processando-as. Os processos podem se comunicar usando memória compartilhada para dados de cache compartilhado, dados de persistência de sessão e outros recursos compartilhados.

Dentro do processo do trabalhador NGINX

O processo de trabalho NGINX é um mecanismo não bloqueante e orientado a eventos para processar solicitações de clientes da Web.

Cada processo de trabalho do NGINX é inicializado com a configuração do NGINX e recebe um conjunto de soquetes de escuta do processo mestre.

Os processos de trabalho do NGINX começam esperando por eventos nos soquetes de escuta ( accept_mutex e fragmentação de soquete do kernel ). Os eventos são iniciados por novas conexões de entrada. Essas conexões são atribuídas a uma máquina de estado – a máquina de estado HTTP é a mais comumente usada, mas o NGINX também implementa máquinas de estado para tráfego de fluxo (TCP bruto) e para vários protocolos de e-mail (SMTP, IMAP e POP3).

Para processar uma solicitação de cliente recebida, o NGINX lê os cabeçalhos HTTP, aplica limites, se configurados, faz redirecionamentos internos e sub-solicitações conforme necessário, encaminha para serviços de back-end, aplica filtros e registra suas ações.

A máquina de estados é essencialmente o conjunto de instruções que informam ao NGINX como processar uma solicitação. A maioria dos servidores web que executam as mesmas funções que o NGINX usam uma máquina de estado semelhante – a diferença está na implementação.

Agendando a Máquina de Estado

Pense na máquina de estados como as regras do xadrez. Cada transação HTTP é um jogo de xadrez. De um lado do tabuleiro de xadrez está o servidor web – um grande mestre que pode tomar decisões muito rapidamente. Do outro lado está o cliente remoto – o navegador da web que acessa o site ou application por meio de uma rede relativamente lenta.

No entanto, as regras do jogo podem ser muito complicadas. Por exemplo, o servidor web pode precisar se comunicar com outras partes (fazendo proxy para um application upstream) ou falar com um servidor de autenticação. Módulos de terceiros no servidor web podem até mesmo estender as regras do jogo.

Uma Máquina de Estado de Bloqueio

Lembre-se de nossa descrição de um processo ou thread como um conjunto independente de instruções que o sistema operacional pode programar para execução em um núcleo de CPU. A maioria dos servidores e applications da web usa um modelo de processo por conexão ou thread por conexão para jogar o jogo de xadrez. Cada processo ou thread contém as instruções para jogar um jogo até o final. Durante o tempo em que o processo é executado pelo servidor, ele passa a maior parte do tempo "bloqueado" – esperando que o cliente conclua seu próximo movimento.

  1. O processo do servidor web escuta novas conexões (novos jogos iniciados pelos clientes) nos soquetes de escuta.
  2. Quando recebe um novo jogo, ele joga esse jogo, bloqueando após cada movimento para esperar pela resposta do cliente.
  3. Após a conclusão do jogo, o processo do servidor web pode esperar para ver se o cliente deseja iniciar um novo jogo (isso corresponde a uma conexão keepalive). Se a conexão for encerrada (o cliente for embora ou ocorrer um tempo limite), o processo do servidor web retornará para escutar novos jogos.

O ponto importante a lembrar é que cada conexão HTTP ativa (cada jogo de xadrez) requer um processo ou thread dedicado (um grande mestre). Esta arquitetura é simples e fácil de estender com módulos de terceiros (‘novas regras’). No entanto, há um enorme desequilíbrio: a conexão HTTP bastante leve, representada por um descritor de arquivo e uma pequena quantidade de memória, mapeia para um thread ou processo separado, um objeto de sistema operacional muito pesado. É uma conveniência de programação, mas é um desperdício enorme.

NGINX é um verdadeiro Grande Mestre

Talvez você já tenha ouvido falar de jogos de exibição simultânea , onde um grande mestre de xadrez joga contra dezenas de oponentes ao mesmo tempo?

Kiril Georgiev
Kiril Georgiev tocou para 360 pessoas simultaneamente em Sófia, Bulgária. Seu placar final foi de 284 vitórias, 70 empates e 6 derrotas.

É assim que um processo de trabalho NGINX joga “xadrez”. Cada trabalhador (lembre-se – geralmente há um trabalhador para cada núcleo da CPU) é um grande mestre que pode jogar centenas (na verdade, centenas de milhares) de jogos simultaneamente.

  1. O trabalhador aguarda eventos nos soquetes de escuta e conexão.
  2. Os eventos ocorrem nos soquetes e o trabalhador os manipula:
    • Um evento no soquete de escuta significa que um cliente iniciou um novo jogo de xadrez. O trabalhador cria um novo soquete de conexão.
    • Um evento em um soquete de conexão significa que o cliente fez uma nova jogada. O trabalhador responde prontamente.

Um trabalhador nunca bloqueia o tráfego de rede, esperando que seu “oponente” (o cliente) responda. Após fazer sua jogada, o trabalhador imediatamente prossegue para outros jogos onde há jogadas esperando para serem processadas, ou dá as boas-vindas a novos jogadores.

Por que isso é mais rápido do que uma arquitetura de bloqueio e multiprocesso?

O NGINX é muito bem dimensionado para suportar centenas de milhares de conexões por processo de trabalho. Cada nova conexão cria outro descritor de arquivo e consome uma pequena quantidade de memória adicional no processo de trabalho. Há muito pouca sobrecarga adicional por conexão. Os processos NGINX podem permanecer fixados nas CPUs. Trocas de contexto são relativamente pouco frequentes e ocorrem quando não há trabalho a ser feito.

Na abordagem de bloqueio de conexão por processo, cada conexão requer uma grande quantidade de recursos adicionais e sobrecarga, e as trocas de contexto (troca de um processo para outro) são muito frequentes.

Para uma explicação mais detalhada, confira este artigo sobre a arquitetura NGINX, por Andrew Alexeev, vice-presidente de desenvolvimento corporativo e cofundador da NGINX, Inc.

Com o ajuste apropriado do sistema , o NGINX pode ser dimensionado para lidar com centenas de milhares de conexões HTTP simultâneas por processo de trabalho e pode absorver picos de tráfego (um fluxo de novos jogos) sem perder o ritmo.

Atualizando a configuração e atualizando o NGINX

A arquitetura de processo do NGINX, com um pequeno número de processos de trabalho, permite uma atualização muito eficiente da configuração e até mesmo do próprio binário NGINX.

O NGINX recarrega sua configuração sem qualquer tempo de inatividade (interrupção do processamento de solicitações).

Atualizar a configuração do NGINX é uma operação muito simples, leve e confiável. Normalmente, isso significa apenas executar o comando nginx -s reload , que verifica a configuração no disco e envia ao processo mestre um sinal SIGHUP.

Quando o processo mestre recebe um SIGHUP, ele faz duas coisas:

  1. Recarrega a configuração e bifurca um novo conjunto de processos de trabalho. Esses novos processos de trabalho começam imediatamente a aceitar conexões e processar tráfego (usando as novas configurações).
  2. Sinaliza os processos de trabalho antigos para saírem normalmente. Os processos de trabalho param de aceitar novas conexões. Assim que cada solicitação HTTP atual é concluída, o processo de trabalho encerra a conexão de forma limpa (ou seja, não há keepalives persistentes). Depois que todas as conexões são fechadas, os processos de trabalho saem.

Esse processo de recarga pode causar um pequeno pico no uso da CPU e da memória, mas geralmente é imperceptível em comparação com a carga de recursos de conexões ativas. Você pode recarregar a configuração várias vezes por segundo (e muitos usuários do NGINX fazem exatamente isso). Muito raramente, surgem problemas quando há muitas gerações de processos de trabalho do NGINX aguardando o fechamento das conexões, mas mesmo esses são resolvidos rapidamente.

O processo de atualização binária do NGINX atinge o Santo Graal da alta disponibilidade: você pode atualizar o software rapidamente, sem nenhuma conexão perdida, tempo de inatividade ou interrupção no serviço.

O NGINX recarrega seu binário sem qualquer tempo de inatividade (interrupção do processamento de solicitações).

O processo de atualização binária é semelhante em abordagem ao recarregamento suave da configuração. Um novo processo mestre NGINX é executado em paralelo com o processo mestre original, e eles compartilham os soquetes de escuta. Ambos os processos estão ativos e seus respectivos processos de trabalho lidam com o tráfego. Você pode então sinalizar ao velho mestre e seus trabalhadores para que saiam graciosamente.

Todo o processo é descrito com mais detalhes em Controlando o NGINX .

Conclusão

O infográfico Inside NGINX fornece uma visão geral de alto nível de como o NGINX funciona, mas por trás dessa explicação simples há mais de dez anos de inovação e otimização que permitem que o NGINX ofereça o melhor desempenho possível em uma ampla variedade de hardware, mantendo a segurança e a confiabilidade que os applications da web modernos exigem.

Se você quiser ler mais sobre as otimizações no NGINX, confira estes excelentes recursos:


"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."