Como, em geral, o Node.js lida com 10.000 solicitações simultâneas?


395

Entendo que o Node.js usa um thread único e um loop de eventos para processar solicitações que processam apenas uma por vez (que é sem bloqueio). Mas, ainda assim, como isso funciona, digamos 10.000 solicitações simultâneas. O loop de eventos processará todas as solicitações? Isso não levaria muito tempo?

Ainda não consigo entender como pode ser mais rápido do que um servidor da web com vários threads. Entendo que o servidor da web multiencadeado será mais caro em recursos (memória, CPU), mas ainda não seria mais rápido? Provavelmente estou errado; explique como esse thread único é mais rápido em muitas solicitações e o que normalmente faz (em alto nível) ao atender a muitas solicitações, como 10.000.

E também, essa linha única será bem dimensionada com essa grande quantidade? Lembre-se de que estou começando a aprender o Node.js.


5
Porque a maior parte do trabalho (movimentação de dados) não envolve a CPU.
precisa saber é

5
Observe também que, apenas porque há apenas um thread executando o Javascript, não significa que não haja muitos outros threads trabalhando.
precisa saber é

Essa questão é muito ampla ou é uma duplicata de várias outras questões.
precisa saber é


Juntamente com o encadeamento único, o Node.js faz algo chamado "E / S não bloqueadora". Aqui é onde toda a mágica é feita #
Anand N

Respostas:


764

Se você precisar fazer essa pergunta, provavelmente não está familiarizado com o que a maioria dos aplicativos / serviços da Web faz. Você provavelmente está pensando que todos os softwares fazem isso:

user do an action
       
       v
 application start processing action
   └──> loop ...
          └──> busy processing
 end loop
   └──> send result to user

No entanto, não é assim que aplicativos da Web, ou qualquer aplicativo com um banco de dados como back-end, funcionam. Os aplicativos da Web fazem isso:

user do an action
       
       v
 application start processing action
   └──> make database request
          └──> do nothing until request completes
 request complete
   └──> send result to user

Nesse cenário, o software gasta a maior parte de seu tempo de execução usando 0% de tempo de CPU aguardando o retorno do banco de dados.

Aplicativo de rede multithread:

Os aplicativos de rede multithread lidam com a carga de trabalho acima da seguinte maneira:

request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request

Portanto, o encadeamento passa a maior parte do tempo usando 0% da CPU aguardando o retorno do banco de dados. Ao fazer isso, eles tiveram que alocar a memória necessária para um encadeamento que inclui uma pilha de programas completamente separada para cada encadeamento etc. Além disso, teriam que iniciar um encadeamento que, embora não seja tão caro quanto iniciar um processo completo, ainda não é exatamente barato.

Loop de evento com leitura única

Como passamos a maior parte do tempo usando 0% da CPU, por que não executar algum código quando não estamos usando a CPU? Dessa forma, cada solicitação ainda terá o mesmo tempo de CPU que os aplicativos multithread, mas não precisamos iniciar um encadeamento. Então fazemos isso:

request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response

Na prática, ambas as abordagens retornam dados com aproximadamente a mesma latência, já que é o tempo de resposta do banco de dados que domina o processamento.

A principal vantagem aqui é que não precisamos gerar um novo encadeamento, portanto não precisamos fazer muito e muito malloc, o que nos atrasaria.

Rosca mágica, invisível

A coisa aparentemente misteriosa é como as duas abordagens acima conseguem executar a carga de trabalho em "paralelo"? A resposta é que o banco de dados é encadeado. Portanto, nosso aplicativo de thread único está realmente aproveitando o comportamento multithread de outro processo: o banco de dados.

Onde a abordagem de falha única falha

Um aplicativo de leitura única falha muito se você precisar fazer muitos cálculos de CPU antes de retornar os dados. Agora, não quero dizer um loop for processando o resultado do banco de dados. Ainda é principalmente O (n). O que quero dizer é fazer transformações de Fourier (codificação mp3, por exemplo), traçado de raios (renderização em 3D) etc.

Outra armadilha dos aplicativos de leitura única é que ele utilizará apenas um único núcleo da CPU. Portanto, se você possui um servidor quad-core (hoje em dia não é incomum), não está usando os outros três núcleos.

Onde a abordagem multithread falha

Um aplicativo multithread falha muito se você precisar alocar muita RAM por thread. Primeiro, o uso da RAM significa que você não pode lidar com tantas solicitações quanto um aplicativo com um único filtro. Pior, malloc é lento. Alocar muitos e muitos objetos (o que é comum em estruturas modernas da Web) significa que podemos acabar sendo mais lentos do que aplicativos com um único filtro. É aqui que o node.js geralmente vence.

Um caso de uso que acaba piorando os multithread é quando você precisa executar outra linguagem de script em seu encadeamento. Primeiro, você geralmente precisa centralizar todo o tempo de execução para esse idioma, depois precisa centralizar as variáveis ​​usadas pelo seu script.

Portanto, se você estiver escrevendo aplicativos de rede em C ou em go ou java, a sobrecarga do encadeamento geralmente não será muito ruim. Se você estiver escrevendo um servidor Web C para servir PHP ou Ruby, é muito fácil escrever um servidor mais rápido em javascript ou Ruby ou Python.

Abordagem híbrida

Alguns servidores da web usam uma abordagem híbrida. Nginx e Apache2, por exemplo, implementam seu código de processamento de rede como um pool de threads de loops de eventos. Cada encadeamento executa um loop de eventos simultaneamente processando solicitações de encadeamento único, mas os pedidos são balanceados por carga entre vários encadeamentos.

Algumas arquiteturas de thread único também usam uma abordagem híbrida. Em vez de ativar vários encadeamentos a partir de um único processo, você pode iniciar vários aplicativos - por exemplo, 4 servidores node.js. em uma máquina quad-core. Em seguida, você usa um balanceador de carga para distribuir a carga de trabalho entre os processos.

Com efeito, as duas abordagens são imagens espelhadas tecnicamente idênticas uma da outra.


106
Esta é de longe a melhor explicação para o nó que li até agora. Que "app single-threaded é realmente alavancar o comportamento de multi-threaded de outro processo: o banco de dados." Fez o trabalho
kenobiwan

e se o cliente estiver fazendo várias solicitações no nó, como, por exemplo, obter um nome e modificá-lo e dizer que essas operações são enviadas ao servidor para lidar com muita rapidez por muitos clientes. como eu poderia lidar com esse cenário?
Remario

3
@CaspainCaldion Depende do que você quer dizer com muito rápido e muitos clientes. No momento, o node.js pode processar mais de 1000 solicitações por segundo e a velocidade é limitada apenas à velocidade da sua placa de rede. Observe que são 1000 solicitações por segundo, não clientes conectados simultaneamente. Ele pode lidar com os 10000 clientes simultâneos sem problemas. O verdadeiro gargalo é a placa de rede.
Slebetman

11
@slebetman, melhor explicação de sempre. Uma coisa, porém, se eu tiver um algoritmo de Aprendizado de Máquina que processe algumas informações e fornece resultados em conformidade, devo usar multi abordagem ou de rosca única rosca
Ganesh Karewad

5
Os algoritmos do @GaneshKarewad usam CPU, serviços (banco de dados, API REST etc.) usam E / S. Se o AI é um algoritmo escrito em js, você deve executá-lo em outro encadeamento ou processo. Se o AI for um serviço em execução em outro computador (como Amazon ou Google ou IBM AI Services), use uma única arquitetura encadeada.
slebetman

46

O que você parece estar pensando é que a maior parte do processamento é tratada no loop de eventos do nó. O nó realmente faz o trabalho de E / S para threads. As operações de E / S normalmente levam ordens de magnitude mais longas que as operações da CPU. Por que a CPU aguarda isso? Além disso, o sistema operacional já pode lidar com tarefas de E / S muito bem. De fato, como o Node não espera, obtém uma utilização muito maior da CPU.

Por analogia, pense no NodeJS como um garçom que recebe os pedidos dos clientes enquanto os chefs de E / S os preparam na cozinha. Outros sistemas têm vários chefs, que solicitam os clientes, preparam a refeição, limpam a mesa e só depois atendem ao próximo cliente.


5
Obrigado pela analogia do restaurante! Acho analogias e exemplos do mundo real muito mais fáceis de aprender.
LaVache

13

Entendo que o Node.js usa um thread único e um loop de eventos para processar solicitações que processam apenas uma por vez (que é sem bloqueio).

Eu poderia estar entendendo mal o que você disse aqui, mas "um de cada vez" parece que você pode não estar entendendo completamente a arquitetura baseada em eventos.

Em uma arquitetura de aplicativo "convencional" (não orientada a eventos), o processo passa muito tempo esperando que algo aconteça. Em uma arquitetura baseada em eventos como o Node.js, o processo não apenas espera, mas pode continuar com outro trabalho.

Por exemplo: você obtém uma conexão de um cliente, aceita, lê os cabeçalhos da solicitação (no caso de http) e começa a agir sobre a solicitação. Você pode ler o corpo da solicitação; geralmente, você envia alguns dados de volta ao cliente (isso é uma simplificação deliberada do procedimento, apenas para demonstrar o ponto).

Em cada um desses estágios, a maior parte do tempo é gasta aguardando a chegada de alguns dados da outra extremidade - o tempo real gasto no processamento no encadeamento JS principal é geralmente bastante mínimo.

Quando o estado de um objeto de E / S (como uma conexão de rede) muda de forma que ele precise ser processado (por exemplo, dados são recebidos em um soquete, um soquete se torna gravável etc.), o principal encadeamento JS do Node.js. é acordado com uma lista de itens que precisam ser processados.

Ele encontra a estrutura de dados relevante e emite algum evento nessa estrutura que faz com que os retornos de chamada sejam executados, que processam os dados recebidos ou gravam mais dados em um soquete etc. Depois que todos os objetos de E / S que precisam de processamento tiverem sido processado, o principal encadeamento JS do Node.js. esperará novamente até que seja informado que mais dados estão disponíveis (ou que alguma outra operação foi concluída ou atingiu o tempo limite).

Na próxima vez em que for ativado, pode ser que um objeto de E / S diferente precise ser processado - por exemplo, uma conexão de rede diferente. A cada vez, os retornos de chamada relevantes são executados e, em seguida, ele volta a dormir esperando que algo mais aconteça.

O ponto importante é que o processamento de solicitações diferentes é intercalado, ele não processa uma solicitação do início ao fim e depois passa para a próxima.

Na minha opinião, a principal vantagem disso é que uma solicitação lenta (por exemplo, você está tentando enviar 1 MB de dados de resposta para um dispositivo móvel por uma conexão de dados 2G ou está fazendo uma consulta muito lenta ao banco de dados) ganhou ' Não bloqueie os mais rápidos.

Em um servidor Web multithread convencional, você normalmente terá um thread para cada solicitação que está sendo processada e processará APENAS essa solicitação até que seja concluída. O que acontece se você tiver muitas solicitações lentas? Você acaba com muitos encadeamentos processando esses pedidos, e outros pedidos (que podem ser pedidos muito simples que podem ser tratados muito rapidamente) ficam na fila atrás deles.

Existem muitos outros sistemas baseados em eventos além do Node.js, e eles tendem a ter vantagens e desvantagens semelhantes em comparação com o modelo convencional.

Eu não diria que os sistemas baseados em eventos são mais rápidos em todas as situações ou em todas as cargas de trabalho - eles tendem a funcionar bem para cargas de trabalho ligadas a E / S, não tão bem para as associadas a CPU.


12

Etapas de processamento do modelo de loop de evento com thread único:

  • Clientes enviam solicitação ao servidor Web.

  • O nó JS Web Server mantém internamente um pool de Encadeamentos limitados para fornecer serviços às Solicitações de Cliente.

  • O nó JS Web Server recebe esses pedidos e os coloca em uma fila. É conhecido como "Fila de eventos".

  • O nó JS Web Server internamente possui um componente, conhecido como "Event Loop". O motivo desse nome é que ele usa loop indefinido para receber solicitações e processá-las.

  • O Loop de Eventos usa apenas um thread único. É o coração principal do Node JS Platform Processing Model.

  • O loop de eventos verifica se qualquer solicitação de cliente é colocada na fila de eventos. Caso contrário, aguarde solicitações recebidas indefinidamente.

  • Se sim, escolha uma solicitação de cliente na fila de eventos

    1. Inicia o processo que a solicitação do cliente
    2. Se essa solicitação do cliente não exigir nenhuma operação de bloqueio de E / S, processe tudo, prepare a resposta e envie-a de volta ao cliente.
    3. Se essa solicitação de cliente exigir algumas operações de bloqueio de E / S, como interagir com banco de dados, sistema de arquivos e serviços externos, ela seguirá uma abordagem diferente
  • Verifica a disponibilidade de Threads no Pool de Threads Internos
  • Pega um segmento e atribui essa solicitação de cliente a esse segmento.
  • Esse segmento é responsável por atender a essa solicitação, processá-la, executar operações de bloqueio de E / S, preparar a resposta e enviá-la de volta ao loop de eventos

    muito bem explicado por @Rambabu Posa para mais explicações vá jogar este link


o diagrama fornecido nessa postagem do blog parece estar errado, o que eles mencionaram nesse artigo não está completamente correto.
rranj 9/02

11

Adicionando à resposta do slebetman: Quando você diz que Node.JSpode lidar com 10.000 solicitações simultâneas, elas são essencialmente solicitações sem bloqueio, ou seja, essas solicitações pertencem principalmente à consulta ao banco de dados.

Internamente, event loopof Node.JSé manipular a thread pool, em que cada thread manipula um non-blocking requestloop e event continua a ouvir mais solicitações após delegar trabalho a um dos threads do thread pool. Quando um dos threads conclui o trabalho, ele envia um sinal para o event loopfinal de um aka callback. Event loopprocesse esse retorno de chamada e envie a resposta de volta.

Como você é novato no NodeJS, leia mais sobre nextTickcomo entender como o loop de eventos funciona internamente. Leia os blogs em http://javascriptissexy.com , eles foram realmente úteis para mim quando eu comecei com JavaScript / NodeJS.


2

Adicionando à resposta do slebetman para mais clareza sobre o que acontece durante a execução do código.

O conjunto de encadeamentos internos em nodeJs possui apenas 4 encadeamentos por padrão. e não é como se a solicitação inteira estivesse anexada a um novo encadeamento do conjunto de encadeamentos, toda a execução da solicitação aconteceria como qualquer solicitação normal (sem nenhuma tarefa de bloqueio), apenas quando uma solicitação tiver uma execução longa ou uma operação pesada como db chamada, uma operação de arquivo ou uma solicitação http, a tarefa é enfileirada no pool de encadeamentos interno fornecido pelo libuv. E como o nodeJs fornece 4 encadeamentos no conjunto de encadeamentos internos, por padrão, cada 5ª ou a próxima solicitação simultânea espera até que um encadeamento esteja livre e, uma vez que essas operações terminam, o retorno de chamada é enviado para a fila de retorno de chamada. e é captado pelo loop de eventos e envia de volta a resposta.

Agora, aqui vem outra informação: não é uma única fila de retorno de chamada, existem muitas filas.

  1. Fila NextTick
  2. Micro fila de tarefas
  3. Fila de temporizadores
  4. Fila de retorno de chamada de E / S (solicitações, operações de arquivo, operações de banco de dados)
  5. Fila de pesquisa de E / S
  6. Verificar fila de fase ou SetImmediate
  7. fechar fila de manipuladores

Sempre que uma solicitação chega, o código é executado nesta ordem de retornos de chamada na fila.

Não é como quando há uma solicitação de bloqueio, ela é anexada a um novo encadeamento. Existem apenas 4 threads por padrão. Portanto, há outra fila acontecendo lá.

Sempre que em um código ocorre um processo de bloqueio como a leitura de arquivos, chama uma função que utiliza o thread do pool de threads e, depois que a operação é concluída, o retorno de chamada é passado para a respectiva fila e depois executado na ordem.

Tudo fica na fila com base no tipo de retorno de chamada e processado na ordem mencionada acima.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.