PracticeEveryday

Nodejs 본문

Nodejs

Nodejs

kimddakki 2022. 5. 14. 01:04
런타임
  • 런타임이란 특정 언어로 만든 프로그램을 실행할 수 있는 환경을 뜻한다.
  • 따라서 노드는 자바스크립트 프로그램을 컴퓨터에서 실행할 수 있게 하는 자바스크립트 실행기이다.
  • 특히 2008년 구글이 V8 엔진을 사용하여 크롬을 출시했고 V8 엔진은 다른 자바스크립트 엔진과 달리 매우 빨라 라이언 달(Ryan Dahl)은 2009년 V8 엔진 기반의 노드 프로젝트를 시작하며 세상에 나왔다.
  • JavaScript는 스크립트 언어이기 때문에 독립적인 프로그램을 만들 수가 없었지만 !! Nodejs가 나오면서 JavaScript라는 언어를 가지고 프로그램을 만들고 실행 시킬 수 있게 되었다!!

Nodejs 특징
1. 이벤트 기반
2. 이벤트 루프
3. 논 브로킹 I/O
4. 싱글 스레드
console.log(1);

setTimeout(() => {
  console.log(2);
}, 10);

console.log(3);

1, 2, 3의 순서가 아니라 => 1, 3, 2의 순서로 나온다.


node.js의 구성요소

Node.js는 V8, LibUv와 같은 라이브러리들로 이루어진 하나의 환경이다

libUv : node.js에서 비동기 처리를 담당하는 라이브러리
V8 : JavaScript 엔진으로써 JavaScript를 실행하는 라이브러리

Node.js가 비동기 처리 될 수 있는 이유는 libUv가 제공하는 이벤트 루프와 시스템 커널 API를 이용하기 때문이다.


libUV

libUv란 C++로 작성된 Node.js가 사용하는 I/O 라이브러리 이다.

이는 사실 운영체제의 커널을 추상화한 Wrapping 라이브러리로 커널이 어떤 비동기 API를 지원하는 지 알고 있다.

우리가 libUv에게 파일 읽기와 같은 비동기 작업을 요청하면 libUv는 이 작업을 커널이 지원하는지 확인한다.

만약 지원한다면 libUv가 대신 커널에게 비동기적으로 요청했다가 응답이 오면 우리에게 전달해 준다.

만약 요창한 작업을 커널이 지원하지 않는 다면 자신만의 워커 스레드가 담긴 스레드 풀을 이용해 해결한다.

 

libUv는 기본적으로 4개의 쓰레드를 가지는 쓰레드 풀을 생성한다. uv_threadpool 환경을 이용해 최대 128개 까지 늘리는 것도 가능하다.

libUv는 운영체제의 커널을 추상화해 비동기 API를 지원한다.
libUv는 커널이 어떤 비동기 API를 지원하는지 이미 알고 있다.
커널이 지원하는 비동기 작업을 libUv에게 요청하면 libUv는 대신해 달라고 커널에게 비동기적으로 요청한다.
커널이 지원하지 않는 비동기 작업을 libUv에게 요청한다면 libUv는 내부에 가지고 있는 스레드 풀 (기본적으로 4개)
에게 이 작업을 요청한다.

어떻게 Node.js는 싱글 스레드로 논 브로킹 비동기 작업을 지원하는가?

Node.js는 I/O 작업을 자신의 메인 스레드가 아닌 다른 스레드에 위임함으로써 논 블로킹 I/O를 지원한다.

 => Node.js는 I/O 작업을 libUv에게 위임함으로써 논 블로킹 I/O를 지원하며 그 기반에는 이벤트 루프가 존재한다.

이벤트 루프는 Node.js가 여러 비동기 작업을 관리하기 위한 구현체다. 

console.log("Hello World")와 같은 동기 작업이 아니라 file.readFile('test.txt', callback)과 같은 비동기 작업들을

모아서 관리하고 순서대로 실행할 수 있게 해주는 도구이며 위와 같이 구성되어있다.

 

// 아래 nextTickQueue와 microTaskQueue는 이벤트 루프의 일부가 아니지만 Node.js의 비동기 작업 관리를 도와주는 것들이다.

 

이벤트 루프 구성
Timer Phase : 단계
Pending: ~ 있을 때 까지 ~ 기다리는 동안 Callbacks Pase
Idle: 완전한 , Prepare: 준비 Phase
Poll: 여론 조사, 투표, 개표 Phase
Check Phase
Close Callbacks Phase

Tick: 째깍거리다 똑딱 거리다 (정답에)체크 표시하다 : 한 페이즈에서 다음 페이즈로 넘어가는 것

각 페이즈들은 자신만의 큐를 하나씩 가지고 있는데 이 큐 안에 이벤트 루프가 실행해야 하는 작업들이 순서대로 담겨 있다.

Node.js가 페이즈에 진입하게 되면 이 큐 내에서 JavaScript 코드( Ex. 콜백)을 꺼내서 하나씩 실행한다.

큐에 있는 작업들을다 실행하거나, 시스템의 실행 한도에 다다르면 Node.js는 다음 페이즈로 넘어간다!

위 그림 처럼 Poll, Check, Close 페이즈가 관리하는 큐에 console.log 콜백이 쌓여 있다면

Node.js가 페이즈의 순서대로 코드를 실행하여 출력한다.

// 1 => 2 => 3 => 4

 

※ 이벤트 루프가 Node.js의 비동기 실행을 도와주는 것을 별개로 싱글 스레드이기에 한 번에 하나의 페이즈에만 진입하여 실행 할 수 있다는 것을 명심해야 한다. Poll Pase 작업을 처리하며 Check Phase의 작업을 동시에 실행하는 등의 작업 처리는 불가능 하다.

 

이벤트 루프는 Node.js가 비동기 작업을 관리하기 위한 구현체다.
이벤트 루프는 총 6개의 페이즈로 구성되어 있으며 한 페이즈에서 다음 페이즈로 넘어가는 것을 틱이라고 한다.
각 페이즈는 자신만의 큐를 관리한다.
Node.js는 순서대로 페이즈를 방문하면서 큐에 쌓인 작업을 하나씩 실행한다.
페이즈의 큐에 담긴 작업을 모두 실행하거나 시스템의 실행 한도에 다다르면 Node.js는 다음 페이즈로 넘어간다.
이벤트 루프가 살아있는 한 Node.js는 이벤트 루프를 반복한다.
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
	// ...
  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);
    // ...
    uv__io_poll(loop, timeout);
	  // ...
    uv__run_check(loop);
    uv__run_closing_handles(loop);
	  // ...
    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }
  return r;
}

// node.js의 event loop 코드 => 반복문 속에서 차례대로 페이즈를 수행한다!

¡ 여기서 주의 해야 하는 점은 한 큐에 3개의 작업이 담겨 있었다고 항상 3개의 작업만 처리하고 다음 페이즈로 넘어가는 것은 아니다!!

 

db.query("SELECT * FROM EVENT_LOOP", (err, data) => {
		console.log(data);
});

SELECT * FROM EVENT_LOOP라는 쿼리를 날린다면 언젠간 데이터베이스는 쿼리 결과를 응답할 것이고 우리는 그 결과를 콜백을 통해 받아 볼 수 있다. 이 코드의 경우 그 콜백은 쿼리 결과를 출력하는 함수가 된다!

이벤트 루프는 비동기 작업들을 관리한다. 위에서 말한 콜백 또한 하나의 비동기 작업이므로 어떤 페이즈가 관리하는 큐에 담기게 된다. 그 페이즈를 A 큐를 Q 콜백을 F라고 했을 때 

A가 관리하는 Q에는 F가 하나 담겨 있다. 그리고 Q에는 오직 F 작업 하나만 담겨 있다고 할 떄

Node.js가 열심히 이벤트 루프를 돌다가 A 페이즈에 진입한다
Node.js는 Q를 확인한다
Q에서 F를 꺼내서 실행한다
Node.js는 Q를 확인한다.
큐가 비어있으니 Node.js는 다음 페이즈로 넘어간다

큐에 한 개의 작업만 있었고 Node.js는 1개의 작업을 실행하고 다음 페이즈로 넘어갔다. 


아지만 아래의 코드와 같을 땐 어떻게 작업할까?

db.query("SELECT * FROM EVENT_LOOP", (err, data) => {
		db.query("SELECT * FROM SPRING_BOOT", (err2, data2) => {
				console.log("Hello World~");
		});
		console.log(data);
});

이번에는 SELECT * FROM EVENT_LOOP 라는 쿼리를 날린 후 응답이 왔을 때 다시 SELECT * FROM SPRING_BOOT라는 새로운 쿼리를 날린다.

처음 실행되는 콜백을 F1, 다음 콜백을 F2라고 할때 아래와 같은 코드로 바꿀 수 있다.

const F2 = (err, data) = {
		console.log("Hello World~");
}
const F1 = (err, data) => {
		db.query("SELECT * FROM SPRING_BOOT", F2);
		console.log(data);
}
db.query("SELECT * FROM EVENT_LOOP", F1);
// query문 SELECT * FROM EVENT_LOOP를 쏘고 성공하면(?) F1을 실행해라

아까 봤던 예제와 똑같이 페이즈 A가 있었고 이 페이즈는 큐 Q를 관리한다. 그리고 그 큐에는 F1 작업 하나가 담겨있다.

Node.js가 열심히 이벤트 루프를 돌다가 A 페이즈에 진입한다
Node.js는 Q를 확인한다.
Q에서 F1를 꺼내서 실행한다.
	SELECT * FROM SPRING_BOOT라는 새로운 쿼리를 날린다
	콘솔에 SELECT * FROM EVENT_LOOP 결과를 출력한다.

 

중간에 쿼리를 하나 더 실행한다는 점만 빼면 아까와 다를 것이 없다.

만약 콘솔에 SELECT * FROM EVENT_LOOP의 결과를 출력하던 중 SELECT * FROM SPRING_BOOT 쿼리의 응답이

온다면 어떻게 될까? 

마침 이 쿼리의 콜백을 다루는 페이즈가 A 였다면 이 쿼리의 콜백 즉, F2는 Q로 들어가게 된다.

Node.js가 열심히 이벤트 루프를 돌다가 A 페이즈에 진입한다
Node.js는 Q를 확인한다.
Q에서 F1를 꺼내서 실행한다.
	SELECT * FROM SPRING_BOOT라는 새로운 쿼리를 날린다

Q에서 F1를 꺼내서 실행한다.
	콘솔에 쿼리 결과를 출력하기 전에 SELECT * FROM SPRING_BOOT의 응답이 와서 
        F2 콜백을 Q에 추가한다
	콘솔에 SELECT * FROM EVENT_LOOP 결과를 출력한다.

Node.js는 Q를 확인한다.
Q에서 F2를 꺼내서 실행한다.
	콘솔에 Hello World~를 출력한다.
Node.js는 Q를 확인한다.
큐가 비어있으니 Node.js는 다음 페이즈로 넘어간다

 - 이 번엔 Node.js가 A 페이즈를 진입 했을 때 1개의 작업만 존재했지만 실제로는 2개의 작업을 수행하고 넘어갔다.

 - 큐의 작업을 꺼내 하나씩 실행하다 보면 큐에 새로운 작업이 들어 올 수 있다.

 - I/O에 대한 콜백이 될 수도 있고 커널이 새로운 스케줄리을 해 줄 수도 있다.

 - 같은 페이즈의 A에서 실행한 작업의 콜백이 A의 큐로 들어갈수도 있고 이전 페이즈인 B에서 예전에 실행했던 작업의

   콜백이 A의 큐로 들어 갈 수도 있다.

 - 여기서의 핵심은 각 페이즈에서 실행한 작업이 또 다른 작업을 스케쥴링 하거나 이전에 처리했던 작업의 이벤트가

   커널에 의해 큐에 추가 될 수 가 있다는 것이다. 극단적으로는 큐에 하나의 작업밖에 없었지만 Node.js는 그 큐에 계속

   해서 추가되는 작업을 처리하느라 다른 페이즈로 이동하지 못할 수도 있다.

 - 그러나 페이즈는 시스템의 실행 한도의 영향을 받기에 작업을 처리하다 포기하고 다음 페이즈로 넘어간다.

 - Node.js가 한 페이즈에 영원히 갇히는 일은 발생하지 않는다!

Node.js는 페이즈에 진입해 큐에 쌓인 작업을 처리한다
쌓인 작업을 처리하던 중 이전 페이즈에서 실행했던 작업의 콜백이나 커널이 스케줄링한 새로운 작업이 큐에 추가될 수 있다.

Node.js가 큐에 계속 추가되는 작업을 처리하느라 다음 페이즈로 넘어가지 못할 수 있다. 
단, 페이즈는 시스템의 실행 한도의 영향을 받으므로 Node.js가 한 페이즈에 영원히 갇히는 일은 없다.

 

 

Node.js 이벤트 루프(Event Loop) 샅샅이 분석하기

글에 들어가기에 앞서 Node.js의 이벤트 루프의 경우 공식 문서에 설명이 부족하고 이에 따라 여러 사람들이 각자 나름대로 분석한 글이 많아 무엇이 이벤트 루프의 정확한 동작인지 알기 힘듭니

www.korecmblog.com

 

'Nodejs' 카테고리의 다른 글

Nodejs  (0) 2022.05.14
Nodejs  (0) 2022.05.14
Nodejs  (0) 2022.05.14
Nodejs  (0) 2022.05.14
Nodejs  (0) 2022.05.13
Comments