socialgekon.com
  • Principal
  • Ágil
  • Outros Do Mundo
  • Pesquisar
  • Tecnologia
Processo Interno

Guia para modelos de servidor de rede de multiprocessamento

Como alguém que escreve código de rede de alto desempenho há vários anos (minha dissertação de doutorado foi sobre o tema de um Servidor de cache para aplicativos distribuídos adaptados a sistemas multicore ), Vejo muitos tutoriais sobre o assunto que omitem ou omitem completamente qualquer discussão sobre os fundamentos dos modelos de servidor de rede. Este artigo, portanto, pretende ser uma visão geral útil e comparação de modelos de servidor de rede, com o objetivo de desvendar um pouco do mistério de escrever código de rede de alto desempenho.

Qual modelo de servidor de rede devo escolher

Este artigo é destinado a 'programadores de sistema', ou seja, desenvolvedores de back-end que trabalhará com os detalhes de baixo nível de seus aplicativos, implementando o código do servidor de rede. Isso geralmente será feito em C ++ ou C , embora hoje em dia a maioria das linguagens e frameworks modernos ofereçam funcionalidade decente de baixo nível, com vários níveis de eficiência.



Vou tomar como conhecimento comum que, uma vez que é mais fácil dimensionar CPUs adicionando núcleos, é natural adaptar o software para usar esses núcleos da melhor maneira possível. Assim, a questão é como particionar o software entre threads (ou processos) que podem ser executados em paralelo em várias CPUs.

Também vou assumir que o leitor está ciente de que 'simultaneidade' basicamente significa 'multitarefa', ou seja, várias instâncias de código (seja o mesmo código ou diferente, não importa), que estão ativas ao mesmo tempo. A simultaneidade pode ser alcançada em uma única CPU e, antes da era moderna, geralmente era. Especificamente, a simultaneidade pode ser alcançada alternando rapidamente entre vários processos ou threads em uma única CPU. É assim que sistemas antigos com uma única CPU conseguiam executar muitos aplicativos ao mesmo tempo, de uma forma que o usuário perceberia como aplicativos sendo executados simultaneamente, embora na verdade não fossem. Paralelismo, por outro lado, significa especificamente que o código está sendo executado ao mesmo tempo, literalmente, por várias CPUs ou núcleos de CPU.

Particionando um aplicativo (em vários processos ou threads)

Para o propósito desta discussão, não é muito relevante se estamos falando sobre threads ou processos completos. Os sistemas operacionais modernos (com a notável exceção do Windows) tratam os processos quase tão leves quanto os threads (ou, em alguns casos, vice-versa, os threads ganharam recursos que os tornam tão pesados ​​quanto os processos). Hoje em dia, a principal diferença entre processos e threads está nas capacidades de comunicação e compartilhamento de dados entre processos ou threads. Onde a distinção entre processos e threads é importante, farei uma observação apropriada, caso contrário, é seguro considerar as palavras 'thread' e 'processo' nesta seção como intercambiáveis.

Tarefas comuns de aplicativos de rede e modelos de servidor de rede

Este artigo trata especificamente do código do servidor de rede, que necessariamente implementa as três tarefas a seguir:

  • Tarefa nº 1: Estabelecimento (e eliminação) de conexões de rede
  • Tarefa 2: Comunicação de rede (IO)
  • Tarefa nº 3: Trabalho útil; ou seja, a carga útil ou o motivo da existência do aplicativo

Existem vários modelos gerais de servidor de rede para particionar essas tarefas nos processos; nomeadamente:

  • MP: Multi-Processo
  • VELOCIDADE: Processo único, orientado a eventos
  • ESTE: Arquitetura baseada em eventos encenados
  • AMPED: Assimétrico multi-processo orientado a eventos
  • SYMPED: Symmetric Multi-Process Event-Driven

Esses são os nomes de modelo de servidor de rede usados ​​na comunidade acadêmica, e lembro-me de encontrar sinônimos 'na selva' para pelo menos alguns deles. (Os nomes em si são, obviamente, menos importantes - o valor real está em como raciocinar sobre o que está acontecendo no código.)

Cada um desses modelos de servidor de rede é descrito com mais detalhes nas seções a seguir.

O modelo multi-processo (MP)

O modelo de servidor de rede MP é aquele que todos costumavam aprender primeiro, especialmente quando aprendiam sobre multithreading. No modelo MP, existe um processo “mestre” que aceita conexões (Tarefa # 1). Depois que uma conexão é estabelecida, o processo mestre cria um novo processo e passa o soquete de conexão para ele, portanto, há um processo por conexão. Esse novo processo geralmente funciona com a conexão de uma maneira simples, sequencial e travada: ele lê algo dela (Tarefa nº 2), depois faz alguns cálculos (Tarefa nº 3) e, em seguida, escreve algo nela (Tarefa nº 2 novamente).

O modelo MP é muito simples de implementar e realmente funciona muito bem, desde que o número total de processos permaneça bastante baixo. Quão baixo? A resposta realmente depende do que as Tarefas 2 e 3 envolvem. Como regra geral, digamos que o número de processos ou threads não deve exceder cerca de duas vezes o número de núcleos da CPU. Uma vez que há muitos processos ativos ao mesmo tempo, o sistema operacional tende a gastar muito tempo se debatendo (ou seja, fazendo malabarismos com os processos ou threads nos núcleos da CPU disponíveis) e tais aplicativos geralmente acabam gastando quase toda a sua CPU tempo no código “sys” (ou kernel), fazendo pouco trabalho realmente útil.

Prós: Muito simples de implementar, funciona muito bem desde que o número de conexões seja pequeno.

Contras: Tende a sobrecarregar o sistema operacional se o número de processos ficar muito grande e pode ter instabilidade de latência à medida que a rede IO espera até que a fase de carga útil (computação) termine.

O modelo orientado a eventos de processo único (SPED)

O modelo de servidor de rede SPED ficou famoso por alguns aplicativos de servidor de rede de alto perfil relativamente recentes, como o Nginx. Basicamente, ele executa todas as três tarefas no mesmo processo, multiplexando entre elas. Para ser eficiente, requer algumas funcionalidades de kernel bastante avançadas como epoll e kqueue . Neste modelo, o código é orientado por conexões de entrada e 'eventos' de dados e implementa um 'loop de evento' semelhante a este:

  • Pergunte ao sistema operacional se há algum novo 'evento' de rede (como novas conexões ou entrada de dados)
  • Se houver novas conexões disponíveis, estabeleça-as (Tarefa nº 1)
  • Se houver dados disponíveis, leia-os (Tarefa # 2) e aja de acordo (Tarefa # 3)
  • Repita até que o servidor saia

Tudo isso é feito em um único processo, e pode ser feito de forma extremamente eficiente, pois evita completamente a troca de contexto entre os processos, o que geralmente mata o desempenho no modelo MP. As únicas alternâncias de contexto aqui vêm de chamadas do sistema, e essas são minimizadas atuando apenas nas conexões específicas que possuem alguns eventos anexados a elas. Este modelo pode lidar com dezenas de milhares de conexões simultaneamente, contanto que o trabalho de carga útil (Tarefa nº 3) não seja excessivamente complicado ou consuma muitos recursos.

Existem duas desvantagens principais, porém, dessa abordagem:

  1. Como todas as três tarefas são realizadas sequencialmente em uma iteração de loop único, o trabalho de carga útil (Tarefa nº 3) é feito de forma síncrona com todo o resto, o que significa que se demorar muito para calcular uma resposta aos dados recebidos pelo cliente, todo o resto para enquanto isso está sendo feito, introduzindo flutuações potencialmente enormes na latência.
  2. Apenas um único núcleo da CPU é usado. Isso tem o benefício, mais uma vez, de limitar absolutamente o número de alternâncias de contexto exigidas do sistema operacional, o que aumenta o desempenho geral, mas tem a desvantagem significativa de que quaisquer outros núcleos de CPU disponíveis não estão fazendo nada.

É por essas razões que modelos mais avançados são necessários.

Prós: Pode ser de alto desempenho e fácil no sistema operacional (ou seja, requer intervenção mínima do sistema operacional). Requer apenas um único núcleo de CPU.

Contras: Utiliza apenas uma única CPU (independentemente do número disponível). Se o trabalho de carga útil não for uniforme, resultará em latência não uniforme de respostas.

O modelo de Staged Event-Driven Architecture (SEDA)

O modelo de servidor de rede SEDA é um pouco complicado. Ele decompõe um aplicativo complexo baseado em eventos em um conjunto de estágios conectados por filas. Se não for implementado com cuidado, entretanto, seu desempenho pode sofrer do mesmo problema do case do MP. Funciona assim:

  • O trabalho de carga útil (Tarefa nº 3) é dividido em tantos estágios, ou módulos, quanto possível. Cada módulo implementa uma única função específica (pense em “microsserviços” ou “microkernels”) que reside em seu próprio processo separado, e esses módulos se comunicam entre si por meio de filas de mensagens. Essa arquitetura pode ser representada como um gráfico de nós, onde cada nó é um processo e as bordas são filas de mensagens.
  • Um único processo executa a Tarefa nº 1 (geralmente seguindo o modelo SPED), que descarrega novas conexões para nós de ponto de entrada específicos. Esses nós podem ser nós de rede puros (Tarefa nº 2), que passam os dados para outros nós para computação, ou podem implementar o processamento de carga útil (Tarefa nº 3) também. Geralmente não há processo 'mestre' (por exemplo, um que coleta e agrega respostas e as envia de volta pela conexão), uma vez que cada nó pode responder por si mesmo.

Em teoria, este modelo pode ser arbitrariamente complexo, com o gráfico de nós possivelmente tendo loops, conexões com outros aplicativos semelhantes ou onde os nós estão realmente executando em sistemas remotos. Na prática, porém, mesmo com mensagens bem definidas e filas eficientes, pode se tornar difícil pensar e raciocinar sobre o comportamento do sistema como um todo. A sobrecarga de passagem de mensagens pode destruir o desempenho deste modelo, em comparação com o modelo SPED, se o trabalho que está sendo feito em cada nó for curto. A eficiência deste modelo é significativamente menor do que a do modelo SPED e, portanto, geralmente é empregado em situações onde o trabalho de carga útil é complexo e demorado.

Prós: O sonho final do arquiteto de software: tudo é segregado em módulos independentes organizados.

Contras: A complexidade pode explodir apenas com o número de módulos, e o enfileiramento de mensagens ainda é muito mais lento do que o compartilhamento direto de memória.

O modelo Asymmetric Multi-Process Event-Driven (AMPED)

O servidor de rede AMPED é uma versão domesticada e mais fácil de modelar do SEDA. Não há tantos módulos e processos diferentes, nem tantas filas de mensagens. É assim que funciona:

  • Implemente as Tarefas # 1 e # 2 em um único processo “mestre”, no estilo SPED. Este é o único processo que faz E / S de rede.
  • Implemente a Tarefa nº 3 em um processo de “trabalho” separado (possivelmente iniciado em várias instâncias), conectado ao processo mestre com uma fila (uma fila por processo).
  • Quando os dados são recebidos no processo “mestre”, encontre um processo de trabalho subutilizado (ou ocioso) e passe os dados para sua fila de mensagens. O processo mestre é enviado pelo processo quando uma resposta está pronta e, nesse ponto, ele passa a resposta para a conexão.

O importante aqui é que o trabalho de carga útil seja executado em um número fixo (geralmente configurável) de processos, que é independente do número de conexões. Os benefícios aqui são que a carga útil pode ser arbitrariamente complexa e não afetará o IO da rede (o que é bom para a latência). Também existe a possibilidade de aumentar a segurança, já que apenas um único processo está realizando IO de rede.

Prós: Separação muito clara de IO de rede e trabalho de carga útil.

Contras: Utiliza uma fila de mensagens para passar dados entre processos, o que, dependendo da natureza do protocolo, pode se tornar um gargalo.

O modelo Symmetric Multi-Process Event-Driven (SYMPED)

O modelo de servidor de rede SYMPED é, em muitos aspectos, o 'Santo Graal' dos modelos de servidor de rede, porque é como ter várias instâncias de processos 'trabalhadores' SPED independentes. É implementado com um único processo aceitando conexões em um loop e, em seguida, transmitindo-as aos processos de trabalho, cada um dos quais com um loop de evento semelhante ao SPED. Isso tem algumas consequências muito favoráveis:

  • As CPUs são carregadas para exatamente o número de processos gerados, que em cada ponto no tempo estão fazendo IO de rede ou processamento de carga útil. Não há como aumentar ainda mais a utilização da CPU.
  • Se as conexões forem independentes (como com HTTP), não haverá comunicação entre processos entre os processos de trabalho.

Isso é, de fato, o que as versões mais recentes do Nginx fazem; eles geram um pequeno número de processos de trabalho, cada um dos quais executa um loop de eventos. Para tornar as coisas ainda melhores, a maioria dos sistemas operacionais fornece uma função pela qual vários processos podem escutar conexões de entrada em uma porta TCP de forma independente, eliminando a necessidade de um processo específico dedicado a trabalhar com conexões de rede. Se o aplicativo no qual você está trabalhando puder ser implementado dessa forma, recomendo fazê-lo.

Prós: Limite máximo de uso de CPU estrito, com um número controlável de loops semelhantes a SPED.

Contras: Como cada um dos processos tem um loop semelhante ao SPED, se o trabalho da carga útil não for uniforme, a latência pode variar novamente, assim como com o modelo SPED normal.

Alguns truques de baixo nível

Além de selecionar o melhor modelo de arquitetura para seu aplicativo, existem alguns truques de baixo nível que podem ser usados ​​para aumentar ainda mais o desempenho do código de rede. Aqui está uma breve lista de alguns dos mais eficazes:

  1. Evite a alocação de memória dinâmica. Como explicação, basta olhar para o código dos populares alocadores de memória - eles usam estruturas de dados complexas, mutexes, e simplesmente há tanto código neles ( jemalloc , por exemplo, tem cerca de 450 KiB de código C!). A maioria dos modelos acima pode ser implementada com rede completamente estática (ou pré-alocada) e / ou buffers que apenas mudam de propriedade entre threads quando necessário.
  2. Use o máximo que o sistema operacional pode fornecer. A maioria dos sistemas operacionais permite que vários processos escutem em um único soquete e implementam recursos em que uma conexão não será aceita até que o primeiro byte (ou mesmo uma primeira solicitação completa!) Seja recebido no soquete. Usar Enviar arquivo() se você puder.
  3. Entenda o protocolo de rede que você está usando! Por exemplo, geralmente faz sentido desabilitar Algoritmo de Nagle , e pode fazer sentido desativar o prolongamento se a taxa de (re) conexão for alta. Aprenda sobre os algoritmos de controle de congestionamento TCP e veja se faz sentido tentar um dos mais novos.

Posso falar mais sobre isso, bem como técnicas e truques adicionais a serem empregados, em uma postagem futura no blog. Mas, por enquanto, espero que isso forneça uma base útil e informativa com relação às escolhas arquitetônicas para escrever código de rede de alto desempenho e suas vantagens e desvantagens relativas.

Como fazer uma transmissão ao vivo no Instagram em 2021

Postagem

Como fazer uma transmissão ao vivo no Instagram em 2021
O Zen de devRant

O Zen de devRant

Estilo De Vida

Publicações Populares
Artista processa Bill Cosby recém-libertado em 1990 em um hotel
Artista processa Bill Cosby recém-libertado em 1990 em um hotel
Um tutorial passo a passo para seu primeiro aplicativo AngularJS
Um tutorial passo a passo para seu primeiro aplicativo AngularJS
Explorando a caixa do urso da bolha da criptomoeda
Explorando a caixa do urso da bolha da criptomoeda
Menos é mais - Usando Lean UX para avaliar a viabilidade do produto
Menos é mais - Usando Lean UX para avaliar a viabilidade do produto
Princípios heurísticos para interfaces móveis
Princípios heurísticos para interfaces móveis
 
Vício de recompra de ações: estudos de caso de sucesso
Vício de recompra de ações: estudos de caso de sucesso
Organizadores do debate: houve 'problemas' com o microfone de Donald Trump
Organizadores do debate: houve 'problemas' com o microfone de Donald Trump
Como criar reflexo de lente em fotos do iPhone e corrigi-lo no Photoshop
Como criar reflexo de lente em fotos do iPhone e corrigi-lo no Photoshop
Como dar feedback sobre design profissional
Como dar feedback sobre design profissional
O verdadeiro ROI da UX: Convencer a Suíte Executiva
O verdadeiro ROI da UX: Convencer a Suíte Executiva
Categorias
Processos FinanceirosFuturo Do TrabalhoMundoÁfrica Do Oriente MédioOutros Do MundoÁsiaSaúdeEuropaDesign MóvelPostagem

© 2023 | Todos Os Direitos Reservados

socialgekon.com