Nodejs 이벤트 루프


141

nodejs 아키텍처에는 내부적으로 두 개의 이벤트 루프가 있습니까?

  • libev / libuv
  • v8 자바 스크립트 이벤트 루프

I / O 요청에서 노드는 libeio에 대한 요청을 대기열에 넣습니다. 그러면 libev를 사용하여 이벤트를 통해 데이터의 가용성을 알리고 결국 해당 이벤트는 콜백을 사용하여 v8 이벤트 루프에 의해 처리됩니까?

기본적으로 libev와 libeio는 nodejs 아키텍처에 어떻게 통합됩니까?

nodejs 내부 아키텍처에 대한 명확한 그림을 제공 할 수있는 문서가 있습니까?

답변:


175

node.js & v8의 소스 코드를 개인적으로 읽었습니다.

네이티브 모듈을 작성하기 위해 node.js 아키텍처를 이해하려고 할 때 비슷한 문제가 발생했습니다.

내가 여기에 게시하는 것은 node.js에 대한 이해이며 이것은 약간 벗어난 것일 수 있습니다.

  1. Libev 는 node.js에서 내부적으로 실행되어 간단한 이벤트 루프 작업을 수행하는 이벤트 루프입니다. 원래 * nix 시스템 용으로 작성되었습니다. Libev는 프로세스가 실행되도록 간단하면서도 최적화 된 이벤트 루프를 제공합니다. libev에 대한 자세한 내용은 여기를 참조 하십시오 .

  2. LibEio 는 입력 출력을 비동기 적으로 수행하는 라이브러리입니다. 파일 디스크립터, 데이터 핸들러, 소켓 등을 처리합니다 . 여기 에서 자세한 내용을 볼 수 있습니다 .

  3. LibUv 는 libeio, libev, c-ares (DNS 용) 및 iocp (Windows asynchronous-io 용) 위에있는 추상화 계층입니다. LibUv는 이벤트 풀에서 모든 io 및 이벤트를 수행, 유지 관리 및 관리합니다. (libeio threadpool의 경우). libUv에 대한 Ryan Dahl의 자습서 를 확인해야합니다 . libUv가 어떻게 작동하는지에 대해 더 이해하기 시작한 다음 libuv 및 v8에서 node.js가 어떻게 작동하는지 이해할 것입니다.

자바 스크립트 이벤트 루프 만 이해하려면 다음 동영상 시청을 고려해야합니다.

비동기 모듈을 작성하기 위해 libeio를 node.js와 함께 사용하는 방법을 보려면 이 예제를 참조하십시오 .

기본적으로 node.js 내에서 발생하는 일은 v8 루프가 실행되고 모든 Javascript 부분뿐만 아니라 C ++ 모듈 (주 문서에서 node.js 자체는 단일 스레드)에서 실행되는 경우입니다. 메인 스레드 외부에있을 때 libev 및 libeio는 스레드 풀에서이를 처리하고 libev는 메인 루프와의 상호 작용을 제공합니다. 내 이해에서 node.js에는 1 개의 영구 이벤트 루프가 있습니다 .v8 이벤트 루프입니다. C ++ 비동기 작업을 처리하기 위해 [libeio & libev를 통해] 스레드 풀을 사용하고 있습니다.

예를 들면 다음과 같습니다.

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

모든 모듈에 나타나는 것은 일반적으로 Task스레드 풀 에서 함수 를 호출하는 것 입니다. 완료되면 AfterTask메인 스레드에서 함수를 호출합니다 . 반면 Eio_REQUEST요청 핸들러는 스레드 풀과 메인 스레드 사이의 통신을 제공하는 동기를 갖는 구조 / 객체 일 수 있습니다.


libuv가 libev를 내부적으로 사용한다는 사실에 의존하는 것은 코드가 플랫폼을 교차하지 않도록하는 좋은 방법입니다. libuv의 공용 인터페이스 만 신경 써야합니다.
Raynos

1
@Raynos libuv는 x-platfousing 다중 라이브러리를 확인하는 것을 목표로합니다. 권리 ? 따라서 libuv를 사용하는 것이 좋습니다
ShrekOverflow

1
@Abhishek From Doc- process.nextTick이벤트 루프 주위의 다음 루프 에서이 콜백을 호출하십시오. 이것은 setTimeout (fn, 0)의 단순한 별칭이 아니며 훨씬 효율적입니다. 이것은 어떤 이벤트 루프를 의미합니까? V8 이벤트 루프?
Tamil


4
이 이벤트를 '보는'방법이 있습니까? 스택에서 호출 순서를 확인하고 새로운 함수가 푸시되어 무슨 일이 일어나고 있는지 더 잘 이해하고 싶습니다. 이벤트 큐에 푸시 된 것을 알려주는 변수가 있습니까?
tbarbe

20

논의 된 실체 (예 : libev 등)가 오랜 시간이라는 사실 때문에 관련성을 잃어 버린 것처럼 보이지만 질문은 여전히 ​​큰 잠재력을 가지고 있다고 생각합니다.

오늘, 추상 유닉스 환경, 노드의 맥락에서 추상 예제를 사용하여 이벤트 중심 모델의 작동을 설명하려고합니다.

프로그램의 관점 :

  • 스크립트 엔진이 스크립트 실행을 시작합니다.
  • CPU 바운드 작업이 발생할 때마다 완전성으로 인라인 (실제 시스템)으로 실행됩니다.
  • I / O 바운드 조작이 발생할 때마다 요청 및 완료 핸들러는 '이벤트 기계류'(가상 기계)에 등록됩니다.
  • 스크립트가 끝날 때까지 위와 동일한 방식으로 작업을 반복하십시오. CPU 바운드 작업-인라인, I / O 바운드 작업을 실행하고 위와 같이 기계에 요청합니다.
  • I / O가 완료되면 리스너가 다시 호출됩니다.

위의 이벤트 기계를 libuv AKA 이벤트 루프 프레임 워크라고합니다. 노드는이 라이브러리를 활용하여 이벤트 중심 프로그래밍 모델을 구현합니다.

노드의 관점 :

  • 런타임을 호스팅 할 스레드가 하나 있습니다.
  • 사용자 스크립트를 선택하십시오.
  • 네이티브로 활용 [레버리지 v8]
  • 바이너리를로드하고 진입 점으로 이동합니다.
  • 컴파일 된 코드는 프로그래밍 프리미티브를 사용하여 CPU 바운드 활동을 인라인으로 실행합니다.
  • 많은 I / O 및 타이머 관련 코드에는 기본 랩이 있습니다. 예를 들어 네트워크 I / O입니다.
  • 따라서 I / O 호출은 스크립트에서 C ++ 브릿지로 라우팅되며 I / O 핸들과 완료 핸들러는 인수로 전달됩니다.
  • 네이티브 코드는 libuv 루프를 연습합니다. 루프를 획득하고 I / O를 나타내는 저수준 이벤트와 원시 콜백 랩퍼를 libuv 루프 구조로 큐에 넣습니다.
  • 기본 코드는 스크립트로 돌아갑니다. 현재 I / O가 수행되지 않습니다!
  • 위의 항목은 모든 비 I / O 코드가 실행될 때까지 여러 번 반복되며 모든 I / O 코드가 등록 될 때까지 libuv가됩니다.
  • 마지막으로 실행할 시스템에 아무것도 남아 있지 않으면 노드는 컨트롤을 libuv로 전달합니다.
  • libuv는 동작을 시작하고 등록 된 모든 이벤트를 선택하고 운영 체제에 쿼리하여 작동 성을 얻습니다.
  • 비 차단 모드에서 I / O 준비가 된 항목을 선택하고 I / O를 수행하며 콜백을 발행합니다. 하나씩 차례로.
  • 아직 준비되지 않은 것 (예를 들어, 다른 엔드 포인트가 아직 아무것도 작성하지 않은 소켓 읽기)은 사용 가능할 때까지 계속 OS와 함께 프로브됩니다.
  • 루프는 내부적으로 계속 증가하는 타이머를 유지합니다. 응용 프로그램이 지연된 콜백 (예 : setTimeout)을 요청하면이 내부 타이머 값을 사용하여 콜백을 발생시키는 데 필요한 시간을 계산합니다.

대부분의 기능은 이러한 방식으로 제공되지만 파일 작업의 일부 (비동기 버전)는 추가 스레드를 사용하여 수행되며 libuv에 잘 통합됩니다. 네트워크 I / O 조작은 데이터 등으로 응답하는 다른 엔드 포인트와 같은 외부 이벤트를 예상하여 대기 할 수 있지만 파일 조작은 노드 자체에서 일부 작업이 필요합니다. 예를 들어, 파일을 열고 fd에 데이터가 준비 될 때까지 기다리면 아무도 실제로 읽지 않으므로 파일이 발생하지 않습니다! 동시에, 메인 스레드의 파일 인라인에서 읽을 경우, 프로그램의 다른 활동을 잠재적으로 차단할 수 있으며 파일 조작이 CPU 바운드 활동에 비해 매우 느리기 때문에 가시적 인 문제점을 야기 할 수 있습니다. 따라서 내부 작업자 스레드 (UV_THREADPOOL_SIZE 환경 변수를 통해 구성 가능)가 파일에서 작동하는 데 사용됩니다.

도움이 되었기를 바랍니다.


이런 것들을 어떻게 알았습니까? 출처를 알려 주시겠습니까?
Suraj Jain

18

libuv 소개

Node.js를 자바 스크립트 환경이 브라우저에서 분리로 프로젝트는 2009 년에 시작되었다. 구글의 사용 V8 과 마크 레만의 libev I의 모델을 결합 Node.js를, / O를 - evented - 잘 프로그래밍의 스타일에 적합 한 언어; 브라우저에 의해 형성된 방식 때문입니다. node.js의 인기가 높아짐에 따라 Windows에서 작동하도록하는 것이 중요했지만 libev는 Unix에서만 실행되었습니다. kqueue 또는 (e) poll과 같은 커널 이벤트 알림 메커니즘에 해당하는 Windows는 IOCP입니다. libuv는 플랫폼에 따라 libev 또는 IOCP를 추상화하여 사용자에게 libev 기반 API를 제공했습니다. libuv libev의 node-v0.9.0 버전 에서 제거되었습니다 .

@ BusyRich의 Node.js의 이벤트 루프를 설명하는 하나의 그림


2017 년 5 월 9 일 업데이트

이 문서 당 Node.js를 이벤트 루프 ,

다음 다이어그램은 이벤트 루프의 작업 순서에 대한 간략한 개요를 보여줍니다.

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

참고 : 각 상자는 이벤트 루프의 "단계"라고합니다.

단계 개요

  • timers :이 단계는 setTimeout()및에 의해 예약 된 콜백을 실행 setInterval()합니다.
  • I / O 콜백 : 클로즈 콜백 , 타이머로 예약 된 콜백 및을 제외한 거의 모든 콜백을 실행 setImmediate()합니다.
  • 유휴, 준비 : 내부에서만 사용됩니다.
  • poll : 새로운 I / O 이벤트를 검색합니다. 적절한 경우 노드가 여기에서 차단됩니다.
  • 확인 : setImmediate()콜백이 여기에서 호출됩니다.
  • 콜백 닫기 : 예 socket.on('close', ...).

이벤트 루프의 각 실행 사이에서 Node.js는 비동기 I / O 또는 타이머를 기다리고 있는지 확인하고없는 경우 깨끗하게 종료됩니다.


" In the node-v0.9.0 version of libuv libev was removed" 을 인용 했지만 nodejs에 이에 대한 설명이 없습니다 changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . 그리고 libev가 제거되면 nodejs에서 비동기 I / O가 어떻게 수행됩니까?
intekhab

@intekhab,이 링크 마다 libeio 기반의 libuv가 node.js에서 이벤트 루프로 사용될 수 있다고 생각합니다.
zangw

@ intekhab 나는 libuv가 I / O 및 폴링과 관련된 모든 기능을 구현한다고 생각합니다. 여기이 문서를 확인하십시오 : docs.libuv.org/en/v1.x/loop.html
mohit kaushik

13

NodeJs 아키텍처에는 하나의 이벤트 루프가 있습니다.

Node.js 이벤트 루프 모델

노드 애플리케이션은 단일 스레드 이벤트 중심 모델에서 실행됩니다. 그러나 노드는 작업을 수행 할 수 있도록 백그라운드에서 스레드 풀을 구현합니다.

Node.js는 이벤트 큐에 작업을 추가 한 다음 이벤트 루프를 실행하는 단일 스레드가이를 선택합니다. 이벤트 루프는 이벤트 큐의 최상위 항목을 잡고 실행 한 후 다음 항목을 가져옵니다.

수명이 길거나 I / O를 차단하는 코드를 실행하면 함수를 직접 호출하는 대신 함수가 완료된 후 실행될 콜백과 함께 함수를 이벤트 큐에 추가합니다. Node.js 이벤트 큐의 모든 이벤트가 실행되면 Node.js 애플리케이션이 종료됩니다.

응용 프로그램 기능이 I / O에서 차단되면 이벤트 루프가 문제를 일으키기 시작합니다.

Node.js는 이벤트 콜백을 사용하여 I / O 차단을 기다릴 필요가 없습니다. 따라서 차단 I / O를 수행하는 모든 요청은 백그라운드의 다른 스레드에서 수행됩니다.

I / O를 차단하는 이벤트가 이벤트 큐에서 검색되면 Node.js는 스레드 풀에서 스레드를 검색하고 기본 이벤트 루프 스레드 대신 해당 함수를 실행합니다. 이는 차단 I / O가 이벤트 큐의 나머지 이벤트를 보류하지 못하게합니다.


8

libuv가 제공하는 이벤트 루프는 하나 뿐이며 V8은 JS 런타임 엔진 일뿐입니다.


1

자바 스크립트 초보자로서 NodeJS에 2 개의 이벤트 루프가 포함되어 있습니까? V8 기고자 중 한 사람과 오랜 연구와 토론 끝에 다음과 같은 개념을 얻었습니다.

  • 이벤트 루프는 JavaScript 프로그래밍 모델의 기본 추상 개념입니다. 따라서 V8 엔진은 임 베더 (브라우저, 노드)가 바꾸거나 확장 할 수있는 이벤트 루프에 대한 기본 구현을 제공합니다 . 여기서 이벤트 루프의 V8 기본 구현을 찾을 수 있습니다.
  • NodeJS에는 이벤트 런타임 이 하나만 있으며 이는 노드 런타임에서 제공합니다. V8 기본 이벤트 루프 구현이 NodeJS 이벤트 루프 구현으로 대체되었습니다.

0

pbkdf2함수에는 JavaScript 구현이 있지만 실제로 수행 할 모든 작업을 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>);

리소스 : https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Libuv 모듈에는 표준 라이브러리의 일부 특정 기능과 관련된 또 다른 책임이 있습니다.

일부 표준 라이브러리 함수 호출의 경우, Node C ++ 측과 Libuv는 이벤트 루프 외부에서 값 비싼 계산을 완전히 결정합니다.

대신 스레드 풀이라는 것을 사용하는 스레드 풀은 pbkdf2함수 와 같이 계산 비용이 많이 드는 작업을 실행하는 데 사용할 수있는 일련의 4 개의 스레드입니다 .

기본적으로 Libuv는이 스레드 풀에 4 개의 스레드를 만듭니다.

이벤트 루프에 사용되는 스레드 외에도 애플리케이션 내부에서 발생하는 고가의 계산을 오프로드하는 데 사용할 수있는 다른 4 개의 스레드가 있습니다.

Node 표준 라이브러리에 포함 된 많은 기능이이 스레드 풀을 자동으로 사용합니다. 그만큼pbkdf2기능은 그들 중 하나를 제공합니다.

이 스레드 풀의 존재는 매우 중요합니다.

따라서 노드는 계산적으로 비싼 작업을 수행하는 데 사용하는 다른 스레드가 있기 때문에 실제로 단일 스레드가 아닙니다.

이벤트 풀이 계산 비용이 많이 드는 작업을 담당하는 경우 노드 응용 프로그램은 다른 작업을 수행 할 수 없습니다.

우리 CPU는 스레드 내부의 모든 명령을 하나씩 실행합니다.

스레드 풀을 사용하면 계산이 진행되는 동안 이벤트 루프 내에서 다른 작업을 수행 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.