Loop de Eventos do Nodejs

141

Existem internamente dois loops de eventos na arquitetura nodejs?

  • libev / libuv
  • loop de evento javascript v8

Em uma solicitação de E / S, o nó enfileira a solicitação ao libeio que, por sua vez, notifica a disponibilidade de dados por meio de eventos usando libev e, finalmente, esses eventos são tratados pelo loop de eventos da v8 usando retornos de chamada?

Basicamente, como o libev e o libeio são integrados na arquitetura nodejs?

Existe alguma documentação disponível para fornecer uma imagem clara da arquitetura interna do nodejs?

tâmil
fonte

Respostas:

175

Eu tenho lido pessoalmente o código fonte do node.js & v8.

Entrei em um problema semelhante a você quando tentei entender a arquitetura node.js. para escrever módulos nativos.

O que estou postando aqui é o meu entendimento do node.js e isso também pode ser um pouco fora de controle.

  1. Libev é o loop de eventos que realmente é executado internamente no node.js para executar operações simples do loop de eventos. Foi escrito originalmente para sistemas * nix. A Libev fornece um loop de eventos simples, porém otimizado, para a execução do processo. Você pode ler mais sobre a libev aqui .

  2. O LibEio é uma biblioteca para executar a saída de entrada de forma assíncrona. Ele lida com descritores de arquivo, manipuladores de dados, soquetes etc. Você pode ler mais sobre isso aqui aqui .

  3. O LibUv é uma camada de abstração na parte superior do libeio, libev, c-ares (para DNS) e iocp (para windows assíncrono-io). O LibUv realiza, mantém e gerencia todos os io e eventos no pool de eventos. (no caso de libeio threadpool). Você deve conferir o tutorial de Ryan Dahl no libUv. Isso começará a fazer mais sentido para você sobre como a libUv funciona e você entenderá como o node.js funciona na parte superior do libuv e da v8.

Para entender apenas o loop de eventos javascript, você deve assistir a esses vídeos

Para ver como o libeio é usado com o node.js para criar módulos assíncronos, você deve ver este exemplo .

Basicamente, o que acontece dentro do node.js é que o loop da v8 executa e manipula todas as partes do javascript, bem como os módulos C ++ [quando eles estão executando em um encadeamento principal (conforme a documentação oficial, o node.js é o único encadeamento)]. Quando fora do encadeamento principal, libev e libeio o tratam no conjunto de encadeamentos e libev fornece a interação com o loop principal. Então, pelo que entendi, o node.js tem um loop de eventos permanente: esse é o loop de eventos da v8. Para lidar com tarefas assíncronas em C ++, ele usa um pool de threads [via libeio & libev].

Por exemplo:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

O que aparece em todos os módulos geralmente chama a função Taskno pool de threads. Quando concluído, ele chama a AfterTaskfunção no thread principal. Considerando que Eio_REQUESTé o manipulador de solicitações que pode ser uma estrutura / objeto cujo motivo é fornecer comunicação entre o conjunto de encadeamentos e o encadeamento principal.

ShrekOverflow
fonte
Confiar no fato de o libuv usar a libev internamente é uma boa maneira de tornar seu código não multiplataforma. Você deve se preocupar apenas com a interface pública do libuv.
Raynos
1
O @Raynos libuv visa garantir que várias bibliotecas sejam armazenadas em x. Certo ? portanto, usando libuv é uma boa idéia
ShrekOverflow
1
@Abhishek Do Doc process.nextTick- No próximo loop ao redor do loop de eventos, chame esse retorno de chamada. Este não é um alias simples para setTimeout (fn, 0), é muito mais eficiente. A qual loop de evento isso se refere? Loop de eventos V8?
Tamil
2
Observe que o libuv não é mais implementado no topo da libev .
Strcat 5/05
4
Existe uma maneira de 'ver' esse evento que? Eu gostaria de poder ver a ordem das chamadas na pilha e ver novas funções sendo empurradas para lá para entender melhor o que está acontecendo ... existe alguma variável que diz o que foi enviado para o evento que?
tbarbe
20

Parece que algumas das entidades discutidas (por exemplo: libev etc.) perderam relevância, devido ao fato de que já faz um tempo, mas acho que a pergunta ainda tem um grande potencial.

Deixe-me tentar explicar o funcionamento do modelo orientado a eventos com a ajuda de um exemplo abstrato, em um ambiente UNIX abstrato, no contexto do Node, a partir de hoje.

Perspectiva do programa:

  • O mecanismo de script inicia a execução do script.
  • Sempre que uma operação ligada à CPU é encontrada, ela é executada em linha (máquina real), em sua integridade.
  • Sempre que uma operação de ligação de E / S é encontrada, a solicitação e seu manipulador de conclusão são registrados com um 'mecanismo de evento' (máquina virtual)
  • Repita as operações da mesma maneira acima até o script terminar. Operação vinculada à CPU - execute em linha, chamadas I / O, solicite ao maquinário como acima.
  • Quando a E / S é concluída, os ouvintes são chamados de volta.

O mecanismo de eventos acima é chamado de estrutura de loop de eventos libuv AKA. O nó utiliza essa biblioteca para implementar seu modelo de programação orientado a eventos.

Perspectiva do nó:

  • Tem um thread para hospedar o tempo de execução.
  • Pegue o script do usuário.
  • Compile-o em nativo [alavancagem v8]
  • Carregue o binário e pule para o ponto de entrada.
  • O código compilado executa as atividades ligadas à CPU em linha, usando primitivas de programação.
  • Muitos códigos relacionados à E / S e ao cronômetro têm envoltórios nativos. Por exemplo, E / S de rede.
  • Portanto, as chamadas de E / S são roteadas do script para as pontes C ++, com o identificador de E / S e o manipulador de conclusão passados ​​como argumentos.
  • O código nativo exercita o loop libuv. Ele adquire o loop, enfileira um evento de baixo nível que representa a E / S e um wrapper de retorno de chamada nativo na estrutura do loop libuv.
  • O código nativo retorna ao script - nenhuma E / S ocorre no momento!
  • Os itens acima são repetidos várias vezes, até que todo o código que não seja de E / S seja executado e todo o código de E / S registrado seja o libuv.
  • Finalmente, quando não há mais nada no sistema para executar, o nó passa o controle para o libuv
  • O libuv entra em ação, captura todos os eventos registrados, consulta o sistema operacional para obter sua operacionalidade.
  • Aqueles que estão prontos para E / S em um modo sem bloqueio, são captados, executados e seus retornos de chamada emitidos. Um após o outro.
  • Aqueles que ainda não estão prontos (por exemplo, uma leitura de soquete, para a qual o outro ponto final ainda não escreveu nada) continuarão sendo analisados ​​no sistema operacional até que estejam disponíveis.
  • O loop mantém internamente um timer cada vez maior. Quando o aplicativo solicita um retorno de chamada adiado (como setTimeout), esse valor interno do timer é aproveitado para calcular o tempo certo para disparar o retorno de chamada.

Embora a maioria das funcionalidades seja atendida dessa maneira, algumas (versões assíncronas) das operações de arquivo são realizadas com a ajuda de threads adicionais, bem integrados ao libuv. Enquanto as operações de E / S de rede podem esperar na expectativa de um evento externo, como o outro terminal respondendo com dados etc., as operações de arquivo precisam de algum trabalho do próprio nó. Por exemplo, se você abrir um arquivo e aguardar que o fd esteja pronto com os dados, isso não acontecerá, pois ninguém está lendo realmente! Ao mesmo tempo, se você ler o arquivo embutido no encadeamento principal, ele poderá bloquear outras atividades no programa e causar problemas visíveis, pois as operações do arquivo são muito lentas se comparadas às atividades vinculadas à CPU. Portanto, os encadeamentos internos do trabalhador (configuráveis ​​através da variável de ambiente UV_THREADPOOL_SIZE) são empregados para operar em arquivos,

Espero que isto ajude.

Gireesh Punathil
fonte
Como você soube dessas coisas, pode me indicar a fonte?
Suraj Jain
18

Uma introdução ao libuv

O projeto node.js começou em 2009 como um ambiente JavaScript dissociado do navegador. Usando o V8 do Google e a libev de Marc Lehmann , o node.js combinou um modelo de E / S - evento - com uma linguagem que era bem adequada ao estilo de programação; devido à forma como foi modelado pelos navegadores. À medida que o node.js crescia em popularidade, era importante fazê-lo funcionar no Windows, mas a libev era executada apenas no Unix. O equivalente do Windows aos mecanismos de notificação de eventos do kernel, como kqueue ou (e) poll, é IOCP. O libuv era uma abstração em torno da libev ou IOCP, dependendo da plataforma, fornecendo aos usuários uma API baseada na libev. Na versão node-v0.9.0 do libuv, a libev foi removida .

Também uma foto que descreve o loop de eventos no Node.js por @ BusyRich


Atualização 05/09/2017

De acordo com este loop de eventos do doc Node.js ,

O diagrama a seguir mostra uma visão geral simplificada da ordem de operações do loop de eventos.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

nota: cada caixa será chamada de "fase" do loop de eventos.

Visão geral das fases

  • temporizadores : esta fase executa retornos de chamada agendados por setTimeout()e setInterval().
  • Retornos de chamada de E / S : executa quase todos os retornos de chamada, com exceção dos retornos próximos , os agendados pelos cronômetros e setImmediate().
  • ocioso, prepare : usado somente internamente.
  • pesquisa : recuperar novos eventos de E / S; nó será bloqueado aqui quando apropriado.
  • verificação : os setImmediate()retornos de chamada são chamados aqui.
  • fechar retornos de chamada : por exemplo socket.on('close', ...).

Entre cada execução do loop de eventos, o Node.js verifica se está aguardando alguma E / S ou temporizadores assíncronos e é encerrado corretamente, se não houver.

zangw
fonte
Você citou isso " In the node-v0.9.0 version of libuv libev was removed", mas não há descrição sobre isso no nodejs changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . E se a libev for removida, agora como a E / S assíncrona está sendo executada no nodejs?
intekhab
@intekhab, por este link , acho que o libuv baseado no libeio poderia ser usado como loop de eventos no node.js.
zangw
@intekhab acho que o libuv está implementando todos os recursos relacionados à E / S e sondagem. aqui, verifique este documento: docs.libuv.org/en/v1.x/loop.html
mohit kaushik
13

Há um loop de eventos na arquitetura do NodeJs.

Modelo de loop de eventos do Node.js.

Os aplicativos de nó são executados em um modelo controlado por evento de thread único. No entanto, o Nó implementa um conjunto de encadeamentos em segundo plano para que o trabalho possa ser executado.

O Node.js adiciona trabalho a uma fila de eventos e, em seguida, tem um único encadeamento executando um loop de eventos. O loop de eventos pega o item superior na fila de eventos, executa e, em seguida, pega o próximo item.

Ao executar um código com vida útil mais longa ou bloqueio de E / S, em vez de chamar a função diretamente, ele adiciona a função à fila de eventos, juntamente com um retorno de chamada que será executado após a conclusão da função. Quando todos os eventos na fila de eventos do Node.js. foram executados, o aplicativo Node.js. é encerrado.

O loop de eventos começa a encontrar problemas quando as funções de nosso aplicativo são bloqueadas na E / S.

O Node.js usa retornos de chamada de evento para evitar ter que esperar pelo bloqueio de E / S. Portanto, todas as solicitações que executam E / S de bloqueio são executadas em um encadeamento diferente em segundo plano.

Quando um evento que bloqueia E / S é recuperado da fila de eventos, o Node.js recupera um encadeamento do conjunto de encadeamentos e executa a função lá, em vez de no encadeamento do loop de eventos principal. Isso evita que a E / S de bloqueio retenha o restante dos eventos na fila de eventos.

Peter Hauge
fonte
8

Há apenas um loop de eventos fornecido pelo libuv, o V8 é apenas um mecanismo de tempo de execução JS.

Warren Zhou
fonte
1

Como iniciante em javascript, eu também tinha a mesma dúvida: o NodeJS contém 2 loops de eventos? Após uma longa pesquisa e discussão com um dos colaboradores da V8, obtive os seguintes conceitos.

  • O loop de eventos é um conceito abstrato fundamental do modelo de programação JavaScript. Portanto, o mecanismo V8 fornece uma implementação padrão para o loop de eventos, que os incorporadores (navegador, nó) podem substituir ou estender . Vocês podem encontrar a implementação padrão V8 do loop de eventos aqui
  • No NodeJS, existe apenas um loop de eventos , que é fornecido pelo tempo de execução do nó. A implementação do loop de eventos padrão da V8 foi substituída pela implementação do loop de eventos do NodeJS
arunjos007
fonte
0

A pbkdf2função possui a implementação JavaScript, mas na verdade delega todo o trabalho a ser feito no lado do C ++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

recurso: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

O módulo Libuv tem outra responsabilidade que é relevante para algumas funções muito particulares na biblioteca padrão.

Para algumas chamadas de função de biblioteca padrão, o lado do Node C ++ e o Libuv decidem fazer cálculos caros fora do loop de eventos completamente.

Em vez disso, eles usam algo chamado pool de threads, o pool de threads é uma série de quatro threads que podem ser usados ​​para executar tarefas computacionalmente caras, como a pbkdf2função.

Por padrão, o Libuv cria 4 threads neste pool de threads.

Além dos encadeamentos usados ​​no loop de eventos, existem outros quatro encadeamentos que podem ser usados ​​para descarregar cálculos dispendiosos que precisam ocorrer dentro de nosso aplicativo.

Muitas das funções incluídas na biblioteca padrão do Nó usam automaticamente esse conjunto de encadeamentos. A pbkdf2função é uma delas.

A presença desse conjunto de encadeamentos é muito significativa.

Portanto, o Node não é verdadeiramente único, porque existem outros segmentos que o Node usa para realizar algumas tarefas caras em termos de computação.

Se o pool de eventos fosse responsável por executar a tarefa computacionalmente cara, nosso aplicativo Node não poderia fazer mais nada.

Nossa CPU executa todas as instruções dentro de um thread, uma por uma.

Usando o pool de threads, podemos fazer outras coisas dentro de um loop de eventos enquanto os cálculos estão ocorrendo.

Daniel
fonte