NodeJS에 대해서 알아보자🤔

average8294·2023년 1월 3일
0

Languages

목록 보기
1/1
post-thumbnail

NodeJS?

Nodejs는 Chrome V8 JavaScript 엔진으로 빌드 된 JavaScript 런타임(환경)입니다.
즉, 노드를 통해 다양한 자바스크립트 애플리케이션을 실행할 수 있으며, 서버를 실행하는 데 가장 많이 사용된다.

내장 HTTP 서버 라이브러리를 포함하고 있어 웹 서버에서 아파치 등의 별도 소프트웨어 없이 동작하는 것이 가능하며, 이를 통한 웹 서버의 동작에 있어 더 많은 통제에서 벗어나 여러 가지 기능을 가능하게 한다.

NodeJS 의존성 구조

공식문서에 따른 NodeJS 구성도를 표현한다면 다음과 같습니다

NodeJS를 구성하는 많은 라이브러리와 도구가 있지만 가장 핵심적인 역할을 담당하는 중요 요소 2가지를 살펴보겠습니다.

  • V8: 구글에 의해 개발된 자바스크립트 엔진으로 heap memory 할당, call stack 실행, garbage collector기능과 JS코드를 기계어로 해석하여 OS가 바로 실행할 수 있는 상태로 만들어주는 것이 주된 업무입니다.

  • libuv: 비동기 I/O를 지원하는 C언어 Library로 윈도우, 리눅스 커널을 Wrapping하여 추상화한 구조로 되어있습니다. 커널의 비동기 API (윈도우- IOCP, 리눅스-AIO) 로 지원할 수 없는 작업을 비동기화 하기 위한 별도의 Thread Pool을 가지고 있고 Event Loop, Event Queue를 관리합니다.

NodeJS 특징

  • Single Thread 기반
  • Event Loop 아키텍처
  • Non Blocking I/O 모델

1. Non-Blocking I/O

전체적인 작업(프로세스)의 흐름을 막느냐 안막느냐 를 말하며 제어권이 어디에 있는지 application(통제할 수 있는) 영역인지 kernel(통제할 수 없는) 영역인지가 중요하다.

non-blocking은 callback을 받아 worker thread pool/kernel(window:IOCP)에 작업을 넘겨주고 바로 제어권을 application영역으로 넘겨주어 작업의 흐름을 blocking하지 않는다.

  • ex) A함수가 I/O작업을 호출했을 때 I/O작업이 완료될때까지 A함수의 작업을 중단하지 않고 I/O 호출에 대해 즉시 리턴하고, A함수가 이어서 다른 일을 수행할 수 있도록 하는 방식을 의미한다.
#수행절차설명
system call / EWOULDBLOCK– 소켓에 datagram 존재 여부 확인– / datagram 없으면 에러 신호 리턴
polling (①번 반복)– 네트워크 소켓에 datagram 존재 여부 지속 확인 (Busy Waiting), 네트워크 소켓으로 datagram 수신 시
copy datagram– 네트워크 소켓의 datagram을 어플리케이션 버퍼로 복사
return data– recvfrom은 성공적으로 리턴, 이후 어플리케이션 데이터 처리

Blocking I/O? Node.js 프로세스에서 추가적인 JavaScript의 실행을 위해 JavaScript가 아닌 작업이 완료될 때까지 기다려야만 하는 상황입니다. 이는 이벤트 루프가 블로킹 작업을 하는 동안 JavaScript 실행을 계속할 수 없기 때문입니다.

2. Single Thread

Node.js는 싱글스레드, 논 블로킹 모델로 싱글 스레드가 혼자서 일을 처리하지만 들어오는 요청 순서가 아닌 논 블로킹 방식으로 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행한다.

setInterval(() => {
  console.log('hi');
}, 1000);
while(true) {
  
}
// 결과는??

결과는 아무것도 찍히지 않습니다. setInterval이 만약 스레드로 동작하여 별도의 실행 컨텍스트를 갖는다면 무한루프의 blocking을 만나더라도 실행에 문제가 없을 것입니다. 하지만 결과는 무한루프의 blocking으로 인해 setInterval의 callback이 실행되지 못합니다. 같은 실행 컨텍스트 안에 있기 때문입니다. 즉, 자바스크립트 실행은 Main Thread에 의해서만 진행된다고 볼 수 있습니다.

만약 동기적 call stack 실행만 있다고 가정 했을때 File 또는 Network와 같은 I/O가 느려진다면 남은 실행은 점점 blocking되어 느려질 것입니다. 해당 병목을 해결하기 위해 NodeJS가 선택한 방식이 비동기 callback 프로그래밍 모델인 Event Loop입니다. Single Thread와 궁합이 좋은 방식입니다.

3. Event Loop

NodeJS에 대한 흔한 오해 중 하나는 자바스크립트 실행을 위한 Main Thread 1개 + Event Loop를 위한 Thread 1개 총 2개의 Thread를 가지고 있다고 생각하는 것입니다. 하지만 이는 사실이 아닙니다. Event Loop는 Main Thread 안에서 실행되며 비동기 callback 작업이 수행될 수 있도록 도와줍니다. Event Loop를 포함한 NodeJS 아키텍처를 다시한번 살펴보겠습니다

libuv 안에 Event Loop + Event Queue + Thread Pool 이 있는걸 보실 수 있을 겁니다. 각각의 요소들을 통해 어떻게 비동기 callback을 수행하는지 그 과정을 같이 알아보겠습니다.

  1. 요청이 들어오면 Event Loop가 해당 요청이 Blocking I/O인지 아닌지 판별합니다.

  2. 커널의 비동기 I/O (윈도우의 IOCP, 리눅스의 AIO)의 지원을 받을 수 있는 Non-Blocking I/O 요청이면 커널의 interface로 해당 요청을 처리 한 후Event Queue에 callback을 등록합니다.

  3. Blocking I/O라면 (예를 들면 File / Network 작업들) libuv 내의 별도의 Thread Pool에서 Worker Thread를 선택하여 작업을 위임합니다. Worker Thread는 작업을 완료한 후 Event Queue로 callback을 등록합니다.

  4. Event Loop는 주기적으로 call stack이 비어있는지 체크하고 Event Queue에 실행 대기중인 callback이 있다면 callback들을 call stack으로 이동시켜 Main Thread에 의해 실행될 수 있게 만들어줍니다.

즉, Event Loop는 각 요청을 특성에 맞게 커널이나 Thread Pool에 위임하고, 실행 대기중인 callback을 Event Queue에 모았다가 Main Thread에 의해 실행될 수 있도록 call stack으로 옮기는 역할을 한다고 볼 수 있습니다.

Event Loop Phases

node main.js

위 명령으로 main.js라는 node application을 실행시키면 가장 먼저 Event Loop가 활성화 상태인지 체크 합니다. 즉, Event Loop가 진행해야 하는 작업이 있다면 Timer 단계부터 Event Loop가 시작되어 Close Callbacks까지 돌고 다음 루프를 돌기전에 계속 해서 Event loop의 상태를 체크합니다. 만약 Event Loop가 활성화 되지 않아도 되는 상태라면 즉, 앞으로 진행해야 할 작업이 없다면 프로세스를 종료하게 됩니다.

  1. Timer
    Timer 단계는 Event Loop의 시작 단계 입니다. 이 단계에서는 setInterval, setTimeout과 같은 타이머에 관련된 callback을 처리합니다. 타이머들이 호출 되자마자 Event Queue에 들어가는 아니고 내부적으로 min-heap 형태로 타이머를 구성하고 있다가 발동 단계가 되면 그때 Event Queue로 callback을 이동시킵니다.

  2. Pending Callbacks
    이 단계에서는 pending_queue에 들어있는 callback들을 실행합니다. pending_queue에는 이전 루프에서 완료된 callback (예를 들면, Network I/O가 끝나고 응답받은 경우) 또는 Error callback 등이 쌓이게 됩니다.

  3. Idle, Prepare
    Idle 에 함축된 뜻과는 다르게 Event Loop가 매번 순회할때마다 실행되며 4번째 단계인 Poll을 위한 준비작업을 하는 단계입니다.

  4. Poll
    대기중인 callback을 call stack으로 가장 많이 올려보내는 단계로 이 단계에서는 새로운 수신 커넥션을 위한 소켓과 데이터를 설정합니다. Poll 단계에서는 watch_queue를 바라보며 작업을 수행하는데 만약, Queue가 비어있지 않다면 배정받은 시간동안 Queue가 모두 소진될 때까지 모든 callback을 call stack으로 올려 실행시킵니다.

  5. Check
    Check 단계는 setImmediate() 만을 위한 단계입니다. 이 단계에서는 setImmediate를 사용하여 수행한 callback만 Event Queue에 쌓이고 call stack으로 올라갑니다.

  6. Close Callbacks
    Close 단계는 아래와 같은 close type의 callback을 관리하는 단계입니다.

각 단계마다 Event Queue를 소유하고 있으며 Event Loop는 각 단계를 돌며 해당 단계의 Event Queue에서 실행할 callback을 call stack으로 이동시킵니다.

정리

  1. Single Thread 기반인 nodejs 는 Event Loop를 추상화한 libuv으 도움을 받아 blocking I/O인지 non-blocking I/O인지 판별합니다.

  2. libuv 내의 Event Loop는 Main Thread에 상주하여 자바스크립트 비동기 실행의 임무를 수행합니다. 요청의 특징에 따라 커널 비동기 함수 또는 libuv내의 Thread Pool에 작업을 위임하며 callback을 실행하기 위해 Event Queue에 적재된 callback을 empty상태의 call stack으로 이동시킵니다.

  3. Event Loop는 6개의 단계로 이루어져 있으며 각 단계별로 Event Queue를 소유합니다. Event Loop는 각 단계를 순차적으로 순회하며 반복적으로 callback들을 처리합니다.

출처 :
https://hanamon.kr/nodejs-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0/
https://medium.com/zigbang/nodejs-event-loop%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-16e9290f2b30
https://akasai.space/node-js/about_node_js_3/
https://etloveguitar.tistory.com/140

0개의 댓글