Desempenho de aplicativos de rede no Linux. Introdução.

As aplicações web são agora utilizadas em todo lugar, e entre todos os protocolos de transporte, o HTTP ocupa a maior parte. Ao estudar as nuances do desenvolvimento de aplicações web, muitos costumam prestar pouca atenção ao sistema operacional onde essas aplicações realmente são executadas. A separação entre desenvolvimento (Dev) e operações (Ops) apenas agravava a situação. Mas com a disseminação da cultura DevOps, os desenvolvedores começaram a assumir a responsabilidade pelo lançamento de suas aplicações na nuvem, portanto, para eles, é muito útil se familiarizar completamente com o back-end do sistema operacional. Isso é especialmente útil se você está tentando implantar um sistema para milhares ou dezenas de milhares de conexões simultâneas.

As limitações em serviços web são muito semelhantes às limitações em outros aplicativos. Seja em balanceadores de carga ou servidores de banco de dados, todos esses aplicativos enfrentam problemas semelhantes em um ambiente de alto desempenho. Compreender essas limitações fundamentais e as maneiras de superá-las permitirá, em geral, avaliar o desempenho e a escalabilidade de suas aplicações web.

Estou escrevendo esta série de artigos em resposta às perguntas de jovens desenvolvedores que desejam se tornar arquitetos de sistemas bem informados. Não é possível entender claramente os métodos de otimização de aplicações Linux sem mergulhar nos fundamentos de como elas funcionam no nível do sistema operacional. Embora existam muitos tipos de aplicações, neste ciclo quero explorar aplicações de rede, e não de desktop, como navegadores ou editores de texto. Este material é voltado para desenvolvedores e arquitetos que desejam entender como programas Linux ou Unix funcionam e como estruturá-los para um alto desempenho.

Linux é um sistema operacional , e na maioria das vezes suas aplicações rodam exatamente neste SO. Embora eu diga "Linux", na maior parte do tempo, você pode presumir com segurança que me refiro a todos os sistemas operacionais semelhantes ao Unix como um todo. No entanto, eu não testei o código associado em outros sistemas. Então, se você está interessado em FreeBSD ou OpenBSD, o resultado pode variar. Quando eu experimento algo específico do Linux, faço questão de apontar isso.

Embora você possa usar os conhecimentos adquiridos para criar um aplicativo do zero e torná-lo perfeitamente otimizado, é melhor não fazer isso. Se você escrever um servidor web em C ou C++ para um aplicativo comercial da sua organização, provavelmente será seu último dia no trabalho. No entanto, conhecer a estrutura desses aplicativos ajudará na escolha de programas já existentes. Você poderá comparar sistemas baseados em processos com sistemas baseados em threads, bem como com sistemas baseados em eventos. Você entenderá e apreciará por que o Nginx funciona melhor do que o Apache httpd e por que um aplicativo Python baseado em Tornado pode atender mais usuários em comparação a um aplicativo Python baseado em Django.

ZeroHTTPd: ferramenta de aprendizado

ZeroHTTPd — um servidor web que eu escrevi do zero em C como ferramenta de aprendizado. Ele não possui dependências externas, incluindo acesso ao Redis. Nós executamos nossas próprias rotinas Redis. Veja mais detalhes abaixo.

Embora pudéssemos discutir a teoria por muito tempo, não há nada melhor do que escrever código, executá-lo e comparar todas as arquiteturas de servidor entre si. Esse é o método mais visual. Portanto, vamos escrever um servidor web simples, o ZeroHTTPd, aplicando cada modelo: baseado em processos, threads e eventos. Vamos testar cada um desses servidores e ver como eles funcionam em comparação uns com os outros. O ZeroHTTPd é implementado em um único arquivo C. O servidor baseado em eventos consiste em uthash, uma excelente implementação de tabela hash que vem em um único arquivo de cabeçalho. Nos demais casos, não há dependências para manter o projeto simples.

O código contém muitos comentários para ajudar a entender. Sendo um servidor web simples composto de algumas linhas de código, o ZeroHTTPd também representa um framework mínimo para desenvolvimento web. Ele tem funcionalidade limitada, mas é capaz de servir arquivos estáticos e páginas 'dinâmicas' muito simples. Devo dizer que o ZeroHTTPd é bastante adequado para ensinar como criar aplicações Linux de alto desempenho. No geral, a maioria dos serviços web aguarda solicitações, verifica-as e as processa. Isso é exatamente o que o ZeroHTTPd fará. É uma ferramenta de aprendizado, e não para produção. Ele não é forte em tratamento de erros e dificilmente se destacará nas melhores práticas de segurança (oh sim, eu usei strcpy) ou com truques complicados da linguagem C. Mas espero que ele faça bem seu trabalho.

Desempenho de aplicativos de rede no Linux. Introdução.
Página inicial do ZeroHTTPd. Ele pode servir diferentes tipos de arquivos, incluindo imagens.

Aplicativo de livro de visitas

Aplicativos web modernos geralmente não se limitam a arquivos estáticos. Eles têm interações complexas com várias bases de dados, caches, etc. Portanto, vamos criar um aplicativo web simples chamado "Livro de Visitas", onde os visitantes podem deixar mensagens sob seus nomes. No livro de visitas, as mensagens anteriores são armazenadas. Também há um contador de visitantes na parte inferior da página.

Desempenho de aplicativos de rede no Linux. Introdução.
Aplicativo web "Livro de Visitas" ZeroHTTPd

O contador de visitantes e as mensagens do livro de visitas são armazenados no Redis. Para a comunicação com o Redis, implementamos procedimentos próprios, que não dependem de bibliotecas externas. Não sou um grande fã de escrever código caseiro quando existem soluções públicas bem testadas. Mas o objetivo do ZeroHTTPd é estudar o desempenho do Linux e o acesso a serviços externos, enquanto o processamento de solicitações HTTP impacta seriamente o desempenho. Precisamos ter controle total sobre as comunicações com o Redis em cada uma de nossas arquiteturas de servidor. Em uma arquitetura, usamos chamadas bloqueantes, em outras, procedimentos baseados em eventos. Usar uma biblioteca cliente externa do Redis não nos dará esse controle. Além disso, nosso pequeno cliente Redis realiza apenas algumas funções (obter, definir e incrementar uma chave; obter e adicionar a um array). Além disso, o protocolo Redis é incrivelmente elegante e simples. Não é necessário estudá-lo especificamente. O fato de que todo o trabalho do protocolo é realizado em cerca de cem linhas de código demonstra quão bem pensado ele é.

Na próxima ilustração, são mostradas as ações do aplicativo quando o cliente (navegador) faz uma solicitação /guestbookURL.

Desempenho de aplicativos de rede no Linux. Introdução.
Mecanismo de funcionamento do aplicativo livro de visitas

Quando precisamos exibir a página do livro de visitas, ocorre uma chamada ao sistema de arquivos para ler o modelo na memória e três chamadas de rede ao Redis. O arquivo do modelo contém a maior parte do conteúdo HTML para a página na captura de tela acima. Também existem marcadores especiais para a parte dinâmica do conteúdo: postagens e contador de visitantes. Nós os obtemos do Redis, inserimos na página e entregamos ao cliente o conteúdo totalmente formado. A terceira chamada ao Redis pode ser evitada, pois o Redis retorna um novo valor da chave ao ser incrementada. No entanto, para o nosso servidor com uma arquitetura assíncrona baseada em eventos, muitas chamadas de rede representam um bom desafio em termos de aprendizado. Assim, descartamos o valor retornado pelo Redis sobre o número de visitantes e o solicitamos em uma chamada separada.

Arquiteturas de Servidor ZeroHTTPd

Estamos construindo sete versões do ZeroHTTPd com funcionalidade idêntica, mas arquiteturas diferentes:

  • Iterativa
  • Servidor Fork (um processo filho por solicitação)
  • Servidor Pré-Fork (forking de processos antecipado)
  • Servidor com Threads (um thread por solicitação)
  • Servidor com Threads Pré-Criadas
  • Arquitetura baseada em poll()
  • Arquitetura baseada em epoll

Medimos o desempenho de cada arquitetura, sobrecarregando o servidor com requisições HTTP. Mas ao comparar arquiteturas com alto grau de paralelismo, o número de requisições aumenta. Testamos três vezes e calculamos a média.

Metodologia de Teste

Desempenho de aplicativos de rede no Linux. Introdução.
Configuração para Teste de Carga do ZeroHTTPd

É importante que, ao executar os testes, todos os componentes não estejam em uma única máquina. Nesse caso, o sistema operacional tem custos indiretos adicionais de agendamento, pois os componentes competem pelo CPU. Medir os custos indiretos do sistema operacional com cada uma das arquiteturas de servidor escolhidas é um dos objetivos mais importantes deste exercício. Adicionar mais variáveis seria prejudicial ao processo. Portanto, a configuração na imagem acima funciona melhor.

O que cada um desses servidores faz

  • load.unixism.net: aqui executamos ab, a ferramenta Apache Benchmark. Ela gera a carga necessária para testar nossas arquiteturas de servidor.
  • nginx.unixism.net: às vezes queremos executar mais de uma instância de um programa de servidor. Para isso, o servidor Nginx com configurações apropriadas funciona como um balanceador de carga, recebendo ab para nossos processos de servidor.
  • zerohttpd.unixism.net: aqui executamos nossos programas de servidor em sete arquiteturas diferentes, uma por vez.
  • redis.unixism.net: neste servidor, o daemon Redis está em execução, onde são armazenados registros do livro de visitas e a contagem de visitantes.

Todos os servidores funcionam em um núcleo de processador. A ideia é avaliar o desempenho máximo de cada uma das arquiteturas. Como todos os programas de servidor são testados em hardware idêntico, esse é o nível básico para sua comparação. Minha instalação de teste consiste em servidores virtuais alugados da Digital Ocean.

O que estamos medindo?

Podemos medir diferentes métricas. Avaliamos o desempenho de cada arquitetura nesta configuração, carregando os servidores com solicitações em diferentes níveis de paralelismo: a carga aumenta de 20 a 15.000 usuários simultâneos.

Resultados dos testes

No gráfico a seguir, é mostrado o desempenho dos servidores em diferentes arquiteturas em vários níveis de paralelismo. No eixo y estão as solicitações por segundo, e no eixo x, as conexões paralelas.

Desempenho de aplicativos de rede no Linux. Introdução.

Desempenho de aplicativos de rede no Linux. Introdução.

Desempenho de aplicativos de rede no Linux. Introdução.

Abaixo está uma tabela com os resultados.

solicitações por segundo

paralelismo
iterativa.
fork
pré-fork
multithread
pré-multithread
poll
epoll

20
7
112
2100
1800
2250
1900
2050

50
7
190
2200
1700
2200
2000
2000

100
7
245
2200
1700
2200
2150
2100

200
7
330
2300
1750
2300
2200
2100

300
–
380
2200
1800
2400
2250
2150

400
–
410
2200
1750
2600
2000
2000

500
–
440
2300
1850
2700
1900
2212

600
–
460
2400
1800
2500
1700
2519

700
–
460
2400
1600
2490
1550
2607

800
–
460
2400
1600
2540
1400
2553

900
–
460
2300
1600
2472
1200
2567

1000
–
475
2300
1700
2485
1150
2439

1500
–
490
2400
1550
2620
900
2479

2000
–
350
2400
1400
2396
550
2200

2500
–
280
2100
1300
2453
490
2262

3000
–
280
1900
1250
2502
grande variação
2138

5000
–
grande variação
1600
1100
2519
–
2235

8000
–
–
1200
grande variação
2451
–
2100

10 000
–
–
grande variação
–
2200
–
2200

11 000
–
–
–
–
2200
–
2122

12 000
–
–
–
–
970
–
1958

13 000
–
–
–
–
730
–
1897

14 000
–
–
–
–
590
–
1466

15 000
–
–
–
–
532
–
1281

A partir do gráfico e da tabela, fica claro que acima de 8000 solicitações simultâneas, restam apenas dois competidores: pré-fork e epoll. Com o aumento da carga, o servidor baseado em poll se comporta pior do que o multithread. A arquitetura de criação prévia de threads compete de forma digna com epoll: isso demonstra o quão bem o núcleo do Linux gerencia um grande número de threads.

Código fonte do ZeroHTTPd

Código fonte do ZeroHTTPd aqui. Para cada arquitetura, há um diretório separado.

ZeroHTTPd
│
├── 01_iterative
│   ├── main.c
├── 02_forking
│   ├── main.c
├── 03_preforking
│   ├── main.c
├── 04_threading
│   ├── main.c
├── 05_prethreading
│   ├── main.c
├── 06_poll
│   ├── main.c
├── 07_epoll
│    └── main.c
├── Makefile
├── public
│   ├── index.html
│   └── tux.png
└── templates
    └── guestbook
        └── index.html

Além das sete diretórios para todas as arquiteturas, há mais duas no diretório raiz: public e templates. No primeiro, está o arquivo index.html e a imagem da primeira captura de tela. Outros arquivos e pastas podem ser colocados lá, e o ZeroHTTPd deve entregar esses arquivos estáticos sem problemas. Se o caminho no navegador corresponder ao caminho na pasta public, o ZeroHTTPd procura o arquivo index.html nesse diretório. O conteúdo do livro de visitas é gerado dinamicamente. Ele possui apenas a página principal, e seu conteúdo é baseado no arquivo 'templates/guestbook/index.html'. O ZeroHTTPd permite adicionar facilmente páginas dinâmicas para expansão. A ideia é que os usuários possam adicionar templates a este diretório e expandir o ZeroHTTPd conforme necessário.

Para compilar todos os sete servidores, inicie make all a partir do diretório raiz — e todas as compilações aparecerão neste diretório. Os arquivos executáveis buscam os diretórios public e templates a partir do diretório de onde são executados.

API do Linux

Para entender as informações neste ciclo de artigos, não é necessário ter um bom conhecimento da API do Linux. No entanto, recomendo ler mais sobre o tema, há muitos recursos disponíveis na rede. Embora abordemos várias categorias da API do Linux, nossa atenção se concentrará principalmente em processos, threads, eventos e na pilha de rede. Além de livros e artigos sobre a API do Linux, recomendo também consultar as páginas de manual para chamadas de sistema e funções de biblioteca utilizadas.

Desempenho e escalabilidade

Uma observação sobre desempenho e escalabilidade. Teoricamente, não há conexão entre os dois. Você pode ter um serviço web que funcione muito bem, com um tempo de resposta de alguns milissegundos, mas que não escalone de forma alguma. Da mesma forma, pode haver um aplicativo web de baixo desempenho que leva vários segundos para responder, mas que escala para dezenas, para lidar com dezenas de milhares de usuários simultâneos. No entanto, a combinação de alto desempenho e escalabilidade é uma combinação poderosa. Aplicativos de alto desempenho consomem recursos de forma econômica e, assim, atendem de maneira eficaz a mais usuários simultâneos em um servidor, reduzindo custos.

Tarefas de CPU e I/O

Finalmente, nas computações, existem sempre dois tipos possíveis de tarefas: para I/O e CPU. Receber solicitações pela internet (entrada e saída de rede), gerenciamento de arquivos (entrada e saída de rede e disco), interações com o banco de dados (entrada e saída de rede e disco) — todas essas são ações de I/O. Algumas consultas ao banco de dados podem sobrecarregar um pouco a CPU (ordenação, cálculo da média de milhões de resultados, etc.). A maioria dos aplicativos da web tem um limite para o I/O máximo, enquanto a CPU raramente é utilizada em sua plena capacidade. Quando você vê que uma tarefa de entrada e saída está utilizando muita CPU, isso geralmente indica uma arquitetura de aplicativo ruim. Isso pode significar que os recursos da CPU estão sendo desperdiçados em gerenciamento de processos e troca de contexto — e isso não é muito útil. Se você estiver fazendo algo como processamento de imagens, conversão de arquivos de áudio ou aprendizado de máquina, então o aplicativo exigirá recursos robustos de CPU. Mas para a maioria dos aplicativos, isso não é o caso.

Mais sobre arquiteturas de servidores

  1. Parte I. Arquitetura Iterativa
  2. Parte II. Servidores Fork
  3. Parte III. Servidores Pré-fork
  4. Parte IV. Servidores com Threads de Execução
  5. Parte V. Servidores com Criação Antecipada de Threads
  6. Parte VI. Arquitetura Baseada em Poll
  7. Parte VII. Arquitetura Baseada em Epoll

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster