Desempenho de aplicativos de rede Linux. Introdução

Os aplicativos da Web são agora usados ​​em todos os lugares e, entre todos os protocolos de transporte, o HTTP ocupa a maior parte. Ao estudar as nuances do desenvolvimento de aplicações web, a maioria das pessoas presta muito pouca atenção ao sistema operacional onde essas aplicações realmente são executadas. A separação entre desenvolvimento (Dev) e operações (Ops) só piorou a situação. Mas com a ascensão da cultura DevOps, os desenvolvedores estão se tornando responsáveis ​​pela execução de seus aplicativos na nuvem, por isso é muito útil que eles se familiarizem completamente com o back-end do sistema operacional. Isto é especialmente útil se você estiver tentando implantar um sistema para milhares ou dezenas de milhares de conexões simultâneas.

As limitações nos serviços web são muito semelhantes às de outras aplicações. Sejam balanceadores de carga ou servidores de banco de dados, todos esses aplicativos apresentam problemas semelhantes em um ambiente de alto desempenho. Compreender essas limitações fundamentais e como superá-las em geral ajudará você a avaliar o desempenho e a escalabilidade de seus aplicativos web.

Estou escrevendo esta série de artigos em resposta a perguntas de jovens desenvolvedores que desejam se tornar arquitetos de sistemas bem informados. É impossível compreender claramente as técnicas de otimização de aplicativos Linux sem mergulhar nos fundamentos de como elas funcionam no nível do sistema operacional. Embora existam muitos tipos de aplicativos, nesta série quero explorar aplicativos baseados na Web em vez de aplicativos de desktop, como navegadores ou editores de texto. Este material é destinado a desenvolvedores e arquitetos que desejam entender como funcionam os programas Linux ou Unix e como estruturá-los para alto desempenho.

Linux é sala de servidores sistema operacional e, na maioria das vezes, seus aplicativos são executados neste sistema operacional. Embora eu diga "Linux", na maioria das vezes você pode assumir com segurança que me refiro a todos os sistemas operacionais do tipo Unix em geral. No entanto, não testei o código que acompanha em outros sistemas. Portanto, se você estiver interessado no FreeBSD ou no OpenBSD, seus resultados podem variar. Quando tento algo específico do Linux, eu indico.

Embora você possa usar esse conhecimento para criar um aplicativo do zero e ele será perfeitamente otimizado, é melhor não fazer isso. Se você escrever um novo servidor web em C ou C++ para o aplicativo de negócios da sua organização, este pode ser seu último dia de trabalho. Porém, conhecer a estrutura desses aplicativos ajudará na escolha dos programas 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 tem um desempenho melhor do que o Apache httpd, por que um aplicativo Python baseado em Tornado pode atender mais usuários em comparação com um aplicativo Python baseado em Django.

ZeroHTTPd: ferramenta de aprendizagem

ZeroHTTPd é um servidor web que escrevi do zero em C como ferramenta de ensino. Não possui dependências externas, incluindo acesso ao Redis. Executamos nossos próprios procedimentos Redis. Veja abaixo para mais detalhes.

Embora pudéssemos discutir longamente a teoria, não há nada melhor do que escrever código, executá-lo e comparar todas as arquiteturas de servidores entre si. Este é o método mais óbvio. Portanto, escreveremos um servidor web ZeroHTTPd simples usando cada modelo: baseado em processo, baseado em thread e baseado em evento. Vamos verificar cada um desses servidores e ver como eles funcionam em comparação entre si. ZeroHTTPd é implementado em um único arquivo C. O servidor baseado em eventos inclui utash, uma excelente implementação de tabela hash que vem em um único arquivo de cabeçalho. Em outros casos, não há dependências, para não complicar o projeto.

Existem muitos comentários no código para ajudá-lo a entender. Sendo um servidor web simples em poucas linhas de código, ZeroHTTPd também é uma estrutura mínima para desenvolvimento web. Possui funcionalidade limitada, mas é capaz de servir arquivos estáticos e páginas "dinâmicas" muito simples. Devo dizer que ZeroHTTPd é bom para aprender como criar aplicativos Linux de alto desempenho. Em geral, a maioria dos serviços web espera por solicitações, verifica-as e processa-as. Isso é exatamente o que o ZeroHTTPd fará. Esta é uma ferramenta de aprendizagem, não de produção. Não é ótimo no tratamento de erros e é improvável que possua as melhores práticas de segurança (ah, sim, eu usei strcpy) ou os truques inteligentes da linguagem C. Mas espero que faça bem o seu trabalho.

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

Aplicativo para livro de visitas

Os aplicativos da web modernos geralmente não estão limitados a arquivos estáticos. Eles têm interações complexas com vários bancos de dados, caches, etc. Portanto, criaremos uma aplicação web simples chamada "Livro de Visitas", onde os visitantes deixam entradas sob seus nomes. O livro de visitas armazena entradas deixadas anteriormente. Há também um contador de visitantes na parte inferior da página.

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

O contador de visitantes e as entradas do livro de visitas são armazenados no Redis. Para comunicações com Redis são implementados procedimentos próprios, que não dependem da biblioteca externa. Não sou um grande fã de lançar código homebrew quando existem soluções publicamente disponíveis e bem testadas. Mas o objetivo do ZeroHTTPd é estudar o desempenho do Linux e o acesso a serviços externos, ao mesmo tempo que atender solicitações HTTP tem um sério impacto no desempenho. Devemos controlar totalmente as comunicações com Redis em cada uma de nossas arquiteturas de servidor. Em algumas arquiteturas utilizamos chamadas de bloqueio, em outras utilizamos procedimentos baseados em eventos. Usar uma biblioteca cliente Redis externa não fornecerá esse controle. Além disso, nosso pequeno cliente Redis executa apenas algumas funções (obter, definir e incrementar uma chave; obter e anexar a um array). Além disso, o protocolo Redis é extremamente elegante e simples. Você nem precisa ensiná-lo especialmente. O próprio fato de o protocolo fazer todo o trabalho em cerca de cem linhas de código mostra como ele é bem pensado.

A figura a seguir mostra o que o aplicativo faz quando o cliente (navegador) solicita /guestbookURL.

Desempenho de aplicativos de rede Linux. Introdução
Como funciona o aplicativo de livro de visitas

Quando uma página do livro de visitas precisa ser emitida, há uma chamada para o sistema de arquivos para ler o modelo na memória e três chamadas de rede para o Redis. O arquivo de modelo contém a maior parte do conteúdo HTML da página da captura de tela acima. Existem também espaços reservados especiais para a parte dinâmica do conteúdo: postagens e contador de visitantes. Nós os recebemos do Redis, inserimos na página e entregamos ao cliente o conteúdo totalmente formado. A terceira chamada ao Redis pode ser evitada porque o Redis retorna o novo valor da chave quando incrementado. Porém, para nosso servidor, que possui uma arquitetura assíncrona baseada em eventos, muitas chamadas de rede são um bom teste para fins de aprendizado. Portanto, descartamos o valor de retorno do Redis do número de visitantes e o consultamos com uma chamada separada.

Arquiteturas de servidor ZeroHTTPd

Estamos construindo sete versões do ZeroHTTPd com a mesma funcionalidade, mas arquiteturas diferentes:

  • Iterativo
  • Servidor Fork (um processo filho por solicitação)
  • Servidor pré-fork (pré-fork de processos)
  • Servidor com threads de execução (uma thread por solicitação)
  • Servidor com criação pré-thread
  • Baseado em arquitetura poll()
  • Baseado em arquitetura epoll

Medimos o desempenho de cada arquitetura carregando o servidor com solicitações HTTP. Mas ao comparar arquiteturas altamente paralelas, o número de consultas aumenta. Testamos três vezes e calculamos a média.

Metodologia de teste

Desempenho de aplicativos de rede Linux. Introdução
Configuração de teste de carga ZeroHTTPd

É importante que ao executar testes, todos os componentes não sejam executados na mesma máquina. Nesse caso, o sistema operacional incorre em sobrecarga adicional de agendamento à medida que os componentes competem pela CPU. Medir a sobrecarga do sistema operacional de cada uma das arquiteturas de servidor selecionadas é um dos objetivos mais importantes deste exercício. Adicionar mais variáveis ​​será prejudicial ao processo. Portanto, a configuração da imagem acima funciona melhor.

O que cada um desses servidores faz?

  • load.unixism.net: É aqui que executamos ab, utilitário Apache Benchmark. Ele gera a carga necessária para testar nossas arquiteturas de servidores.
  • nginx.unixism.net: Às vezes queremos executar mais de uma instância de um programa de servidor. Para fazer isso, o servidor Nginx com as configurações apropriadas funciona como um balanceador de carga vindo de ab aos nossos processos de servidor.
  • zerohttpd.unixism.net: Aqui executamos nossos programas de servidor em sete arquiteturas diferentes, uma de cada vez.
  • redis.unixism.net: Este servidor executa o daemon Redis, onde as entradas do livro de visitas e os contadores de visitantes são armazenados.

Todos os servidores são executados no mesmo núcleo do processador. A ideia é avaliar o desempenho máximo de cada arquitetura. Como todos os programas de servidor são testados no mesmo hardware, esta é uma base para comparação. Minha configuração de teste consiste em servidores virtuais alugados da Digital Ocean.

O que estamos medindo?

Você pode medir diferentes indicadores. Avaliamos o desempenho de cada arquitetura em uma determinada configuração carregando os servidores com solicitações em diferentes níveis de paralelismo: a carga cresce de 20 para 15 usuários simultâneos.

Resultados do teste

O gráfico a seguir mostra o desempenho de servidores em diferentes arquiteturas em diferentes níveis de paralelismo. O eixo y é o número de solicitações por segundo, o eixo x são conexões paralelas.

Desempenho de aplicativos de rede Linux. Introdução

Desempenho de aplicativos de rede Linux. Introdução

Desempenho de aplicativos de rede Linux. Introdução

Abaixo está uma tabela com os resultados.

solicitações por segundo

simultaneidade
iterativo
garfo
pré-garfo
transmissão
pré-transmissão
pol
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 propagação
2138

5000
-
grande propagação
1600
1100
2519
-
2235

8000
-
-
1200
grande propagação
2451
-
2100

10 000
-
-
grande propagação
-
2200
-
2200

11 000
-
-
-
-
2200
-
2122

12 000
-
-
-
-
970
-
1958

13 000
-
-
-
-
730
-
1897

14 000
-
-
-
-
590
-
1466

15 000
-
-
-
-
532
-
1281

Pelo gráfico e tabela pode-se observar que acima de 8000 solicitações simultâneas restam apenas dois players: pré-fork e epoll. À medida que a carga aumenta, um servidor baseado em poll tem um desempenho pior do que um servidor de streaming. A arquitetura de pré-criação de threads é um concorrente digno do epoll, uma prova de quão bem o kernel do Linux agenda um grande número de threads.

Código-fonte ZeroHTTPd

Código-fonte ZeroHTTPd aqui. Existe um diretório separado para cada arquitetura.

ZeroHTTPd │ ├── 01_iterativo │ ├── main.c ├── 02_forking │ ├── main.c ├── 03_preforking │ ├── main.c ├── 04 _ rosqueamento │ ├── main.c ├── 05_prethreading │ ├── main.c ├── 06_poll │ ├── main.c ├── 07_epoll │ └── main.c ├── Makefile ├── público │ ├── índice .html │ └── tux .png └── modelos └── livro de visitas └── index.html

Além de sete diretórios para todas as arquiteturas, há mais dois no diretório de nível superior: público e modelos. O primeiro contém o arquivo index.html e a imagem da primeira captura de tela. Você pode colocar outros arquivos e pastas lá, e o ZeroHTTPd deve servir esses arquivos estáticos sem problemas. Se o caminho no navegador corresponder ao caminho na pasta pública, o ZeroHTTPd procurará o arquivo index.html neste diretório. O conteúdo do livro de visitas é gerado dinamicamente. Possui apenas uma página inicial e seu conteúdo é baseado no arquivo 'templates/guestbook/index.html'. ZeroHTTPd adiciona facilmente páginas dinâmicas para extensão. A ideia é que os usuários possam adicionar modelos a este diretório e estender o ZeroHTTPd conforme necessário.

Para construir todos os sete servidores, execute make all do diretório de nível superior - e todas as compilações aparecerão neste diretório. Os arquivos executáveis ​​procuram os diretórios public e templates no diretório a partir do qual são iniciados.

API do Linux

Você não precisa ser bem versado na API do Linux para entender as informações desta série de artigos. No entanto, recomendo ler mais sobre este tópico; existem muitos recursos de referência na Internet. Embora iremos abordar diversas categorias de APIs do Linux, nosso foco será principalmente em processos, threads, eventos e pilha de rede. Além de livros e artigos sobre a API do Linux, também recomendo a leitura de mana 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 eles. Você pode ter um serviço web que funcione muito bem, com um tempo de resposta de alguns milissegundos, mas que não seja escalável. Da mesma forma, pode haver um aplicativo Web com desempenho insatisfatório que leva alguns segundos para responder, mas é dimensionado em 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 muito poderosa. Aplicações de alto desempenho geralmente utilizam recursos com moderação e, assim, atendem com eficiência mais usuários simultâneos no servidor, reduzindo custos.

Tarefas de CPU e E/S

Finalmente, na computação sempre existem dois tipos possíveis de tarefas: para E/S e CPU. Receber solicitações pela Internet (E/S de rede), servir arquivos (E/S de rede e disco), comunicar-se com o banco de dados (E/S de rede e disco) são todas atividades de E/S. Algumas consultas ao banco de dados podem consumir um pouco da CPU (classificação, média de um milhão de resultados, etc.). A maioria dos aplicativos da Web é limitada pelo máximo de E/S possível e o processador raramente é usado em capacidade total. Quando você percebe que alguma tarefa de E/S está usando muita CPU, é provavelmente um sinal de arquitetura de aplicativo ruim. Isso pode significar que os recursos da CPU são desperdiçados no gerenciamento de processos e na alternância de contexto - e isso não é totalmente útil. Se você estiver fazendo algo como processamento de imagem, conversão de arquivo de áudio ou aprendizado de máquina, o aplicativo exigirá recursos poderosos de CPU. Mas para a maioria das aplicações este não é o caso.

Saiba mais sobre arquiteturas de servidores

  1. Parte I: Arquitetura Iterativa
  2. Parte II. Servidores bifurcados
  3. Parte III. Servidores pré-fork
  4. Parte IV. Servidores com threads de execução
  5. Parte V. Servidores pré-threaded
  6. Parte VI. Arquitetura baseada em Pol
  7. Parte VII. arquitetura baseada em epoll

Fonte: habr.com

Adicionar um comentário