자바스크립트를 개조해 만들어졌다. 자바스크립트는 독립적인 언어가 아니라 스크립트 언어로 특정 프로그램, 그 중에서도 웹 브라우저안에서만 동작한다. 그런데, node.js를 사용하면 터미널 프로그램에서 node를 입력해 브라우져 없이 바로 실행할 수 있다. node.js는 자바스크립트에서 분리된 언어로 문법이 같다. 즉, node.js를 사용해 자바스크립트가 웹브라우저에서만 동작하는 한계를 극복해 프로그램을 만들 수 있게 되었다. 특히, node.js를 사용해 서버를 만들 수 있게 됨으로써 자바스크립트라는 하나의 언어를 사용해 웹페이지를 만들 수 있게 되었다.
동작하는 웹 서버에 대해 알아보면, A라는 클라이언트로부터 온 요청을 다른 서버에 또다른 요청을 보내거나 db에 쿼리를 날린다. 이때 A에게 온 요청을 처리해 응답하는데 3초라는 시간이 걸린다고 할때, B,C,D,E,...등 10개의 클라이언트에서 요청이 온다면 10번째 클라이언트는 30초 후에야 응답을 받게 될 것이다.
Tomcat서버를 사용하는데, 이 웹서버는 멀티스레드를 가져 A 클라이언트가 보낸 요청은 하나의 스레드를 갖고, 10번째 클라이언트는 10번째 스레드를 갖는다. 따라서 10개의 클라이언트가 동시에 요청을 보내더라도 각각 3초만에 응답을 받게 될 것이다.
그런데, Tomcat 서버는 기본적으로 200개의 스레드를 가지므로 200개 이상의 요청이 들어올 경우, 싱글 스레드와 마찬가지로 딜레이가 발생할 수 있다. (또는, 서버 자체를 증설할 수도 있다.)
node.js는 자바스크립트를 동작하는데, 자바스크립트는 멀티 스레드가 아니다. 즉, node.js는 싱글 스레드로 동작하면서 동시에 여러 요청을 처리할 수 있어야한다. 따라서 node.js 웹서버는 다음과 같은 특징을 갖는다.
none-blocking I/O (하나의 요청 처리하는 동안 다른 요청을 블로킹하지 않는다는 의미)
node.js의 싱글 스레드는 클라이언트로부터 요청을 받으면, 그 요청을 다른 일꾼에게 전달한다. 따라서, 싱글 스레드는 자유로워지고, 두번째 요청을 받을 수 있다. 두번쨰 요청을 받은 경우, 또다시 다른 일꾼에게 전달한다. 그리고 여기서 그 일꾼은 다른 서버 또는 db에 쿼리를 날려 요청을 처리한다.
asynchronous
앞서 말한 클라이언트 요청을 처리한 일꾼이 응답을 가져왔다면,(일을 할당받은 일꾼이 서버 또는 db에 쿼리를 날려 응답을 받아왔다는 뜻) 콜백 함수를 실행하게 된다.
어떻게 멀티 스레드처럼 일꾼들을 데리고 동작할 수 있는걸까? 먼저 node.js의 구조부터 살펴보자. node.js는 v8이라는 자바스크립트 엔진과 비동기 작업을 처리하는 libuv라는 라이브러리로 이루어져있다.
먼저 v8에는 memory heap과 하나의 call stack이 있다. (여기서 하나의 call stack이 있다는 것과 싱글 스레드는 같은 의미로 이해할 수 있다) memory heap은 메모리 할당이 일어나는 곳이고, call stack은 코드 실행에 따라 호출 스택이 쌓인다. call stack은 차례대로 실행되기 때문에 비동기 처리를 할 수 없다.
따라서, 비동기 작업을 가능하게 하는 것은 바로 libuv라는 라이브러리에서 non-blocking IO라는 기능을 가능하게 하는 이벤트 루프를 제공하기 때문이다. libuv는 c언어로 생성되었고, 시스템 커넬을 이용하는데, 커넬은 멀티 스레드를 이용한다. 따라서, node.js는 libuv가 멀티 스레드로 동작하기 때문에 비동기 처리를 할 수 있다. 아래 이미지의 internal C++ Thread Pool이 멀티 스레드이다.
다음과 같은 단계로 동작한다.
STEP 1
node.js API로 요청이 들어오면, 들어온 요청은 event queue에 추가된다.
STEP 2
node.js의 이벤트 루프는 event queue를 살펴, 들어온 요청이 있다면 선착순으로 (first come first served) 요청이 처리된다.
STEP 3
요청은 internal C++ Thread Pool로 보내진다. 이것은 Libuv에서 개발된 이벤트 루프의 일부로 여러 요청을 수행할 수 있다. 동시에 이벤트 루프는 event queue에 요청이 있는지 계속해서 확인한다. 요청이 있다면 thread pool로 가져온다.
STEP 4
thread pool은 db 또는 file 또는 다른 서버 등에 보낸 요청을 수행한다.
STEP 5
수행을 마치면, 콜백 함수를 실행시켜 이벤트 루프로 응답을 전달한다.
STEP 6
이벤트 루프는 응답을 클라이언트에 보낸다. (요청-응답 끝)
node.js는 자바스크립트 이벤트 기반 모델에서 영감을 받은 이벤트 루프 모델이 있는 단일 스레드이다. 따라서 자바스크립트와 유사한 단일 스레드이지만, 네트워크 호출, 파일시스템 작업 등과 같이 비동기적으로 수행되는 작업은 자바스크립트 코드가 아니라 thread pool에서 실행된다. 따라서 node.js는 thread pool을 가지고 있으므로 멀티 스레드의 개념도 가지고 있지만, 사용되는 자바스크립트 환경에서는 싱글 스레드로 사용된다고 할 수 있다.
reference
- https://www.youtube.com/watch?v=YSyFSnisip0
- https://medium.com/@gwakhyoeun/%EC%99%9C-node-js%EB%8A%94-single-thread-%EC%9D%B8%EA%B0%80-bb68434027a3
- https://www.youtube.com/watch?v=8aGhZQkoFbQ
- https://www.c-sharpcorner.com/article/node-js-event-loop/
- https://velog.io/@seungmini/Node.js-%EB%8A%94-Single-Thread-%EC%9D%B8%EA%B0%80
- https://www.geeksforgeeks.org/why-node-js-is-a-single-threaded-language/
정말 감사드립니다! 덕분에 최근에 봤던 기술면접에서 콜스택부터 이벤트 루프,이벤트 큐와 non-blocking I/O 관련 기술면접 답변을 잘 할 수 있었습니다!