예전에 선배 개발자분께서 말씀하셨는게
어떠한 기술을 배울 때 그 기술의 구조나 생명주기 등 내부를 한번 공부하고 해보면 괜찮다 라고 했던말이 있었다.
그 방법을 나에게도 적용시켜 보려고 했고, 현재는 한번은 공부하고 넘어가는게 직성에 풀린다.
최근 이직때문에 시작한 node.js의 구조를 한번 정리하려 한다.
node.js 구조공식 홈페이지에서는 다음과 같이 말한다.
Node.js®는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임입니다.
런타임환경 그 자체라고 한다.
짧은 한마디로는 감이 잡히지 않는다.
node.js는 Single-Thread의 non-blocking I/O 이벤트 기반 비동식 방식으로 작동node.js는 application 자체에서는 Single-Thread로 실행되지만 Background에서 thread pool을 구성해 작업을 수행JavsScript 엔진(V8 Engine)으로 빌드 된 JavaScript 런타임특징을 크게 보자면 이렇게 있는거 같다.
장단점까지도 같이 보자면
npm(node package manager)을 통한 다양한 모듈(패키지) 제공C++로 개발됌) 엔진은 구글이 계속적인 업그레이드 중특징과 장단점만 봤을때는 내가 왜 여태 공부하지 않았는지 살짝 생각이 든다.

출처 : 빨간색코딩
크게 구조적으로 나눠보자면
libuv, V8 Engine, event loop 가 있겠다.
비동기 I/O 작업을 도와주는 멀티 플랫폼 라이브러리입니다. libuv는 C++로 작성되었고 node.js가 사용하는 비동기 I/O라이브러리로 비동기 작업이 어떤 커널이 지원하는지 확인합니다.
JavaScript로 코드를 작성하고 실행하게 되면 스택에 코드가 쌓입니다.
이때 스택에 쌓인 코드를 실행하게 되면 libuv를 호출합니다.
libuv는 비동기 처리를 할지 동기 처리를 할지 검사 후 시스템 API를 이용하거나 worker thread pool(default : 4개)에 생성된 thread에게 작업을 위임합니다.
작업이 완료되면 콜백 함수를 테스크 큐에 넘겨줍니다.
이벤트 루프는 콜스택에 쌓여있는 함수가 없을 때, 테스크 큐에 대기하고 있던 콜백함수를 콜스택으로 넘겨줍니다.
콜스택에 쌓인 콜백 함수가 실행되고, 콜스택에서 제거됩니다
웹 브라우저 내부에서 JavaScript 속도의 개선을 목표로 처음 고안
속도 향상을 위해 V8은 인터프리터를 사용하는 대신 자바스크립트 코드를 더 효율적인 머신 코드로 번역합니다. JIT(Just In Time) 컴파일러를 구현함으로써 코드를 실행 시에 자바스크립트 코드를 머신 코드로 컴파일하는데, 이는 스파이더몽키나 리노와 같은 현대적인 다른 자바스크립트 엔진에서도 마찬가지입니다.
주된 차이는 V8은 바이트코드와 같은 중간 코드를 생산하지 않는다는 점입니다.
사람이 프로그래밍한 프로그램과 CPU에서 사용할 수 있는 기계어의 중간 상태의 코드를 뜻한다. 프로그램을 기계어로 번역하고 최적화하기 위해서는 먼저 중간 코드로 번역하고, 중간 코드를 최적화한 뒤에 기계어로 번역하게 된다.
인터프리터와 컴파일의 장점을 합쳐놓은 컴파일러라 생각하면 편하다.
컴파일 방식은 기계어로 컴파일 된 코드를 바로 실행하므로 빠른 실행 속도를 보장 받는다.
인터프리트 방식은 소스가 수정돼도 귀찮게 매번 컴파일을 하지 않아도 된다는 장점이 존재한다.
이 JIT 컴파일 방식은 프로그램을 실행하는 시점에서 필요한 부분을 즉석에서 컴파일하는 방식이라 동적 컴파일(Dynamic Compile)이라고도 부른다.
이벤트루프는 timers => pending callbacks => idle, prepare -> poll => check => close callbacks 순으로 호출
각각의 단계는 자신만의 queue를 보유. 우리가 등록한 작업 각각의 유형에 맞는 큐에 등록 이 큐가 실행되고 완료되면 다시 이벤트루프로 넘어와 실행
timers
타이머에 관한 비동기 작업을 관리 (setTimeout , setInterval)
이 타이머 콜백은 min-heap 자료구조 기반으로 구성
pending callbacks
처리하지 못하고 넘어간 작업을을 쌓아놓고 실행하는 단계
이벤트 루프에 pending_queue에 들어가있는 콜백들을 실행합니다. 에러 핸들러 콜백도 여기로 들어옴
idle, prerare
node.js내부 관리를 위해 사용
poll
대부분 콜백이 여기에 해당.
예)
-- 1.데이터베이스에 쿼리를 보낸 후 결과가 왔을 떄 실행되는 콜백
-- 2.HTTP 요청을 보낸 후 응답이 왔을 때 실행되는 콜백
-- 3.파일을 비동기로 다시 읽고 다 읽었을 때 실행되는 콜백
watcher_queue를 이용하여 관리. 해당 큐를 사용하는 이유는 비동기 작업이 완료되었을 경우, 순서를 보장하기 위함.
watcher은 FD(File Descriptor)를 가진다. 운영체제가 FD가 준비되었다고 알리면 event loop 이에 해당하는 watcher를 찾을 수 있고 콜백을 실행
check
setImmediate()로 등록된 콜백을 관리하기위한 단계
close callbacks
close, destroy 와 같은 이벤트 타입 콜백을 처리
이벤트 루프는 이 6단계를 라운드 로빈 방식으로 순회하며 동작합니다.
라운드 로빈 스케줄링(Round Robin Scheduling, RR)은 시분할 시스템을 위해 설계된 선점형 스케줄링의 하나로서, 프로세스들 사이에 우선순위를 두지 않고, 순서대로 시간단위(Time Quantum)로 CPU를 할당하는 방식의 CPU 스케줄링 알고리즘이다.
아무래도 non-blocking asynchronous 방식으로 작동을 하다보니
blocking, non-blocking, synchronous, asynchronous 의 개념을 정리해볼까 한다.
말로만 보면 정말 헷갈릴만 해서 인파 님의 블로그에서 예시를 가져와봤다.
요청받은 함수의 작업이 끝나야 제어권을 돌려받음 + 요청자는 결과가 나올때까지 계속 확인

제어권은 바로 돌려줌 + 요청자는 결과가 나올때까지 계속 확인

제어권은 바로 돌려줌 + 결과는 요청받은 함수가 알아서 알려줌

거의 쓰이지 않는다. 성능차이는 블로킹 +동기와 거의 비슷해서 사용하는 경우는 거의없다.
대표적인 예로는 node.js (비동기) + MySQL (동기) 조합이 있다.
정리를 하자면,
블로킹/논블로킹
= 요청받는 함수가 제어권(함수실행권)을 언제 넘겨주느냐의 차이
블로킹 : 요청받는 함수가 작업을 모두 마치고 나서야 요청자에게 제어권을 넘겨줌 (그동안 요청자는 아무것도 하지않고 기다림)
논블로킹 : 요청받은 함수가 요청자에게 제어권을 바로 넘겨줌 (그동안 요청자는 다른 일을 할 수 있음)
동기/비동기
= 요청받은 함수가 작업을 완료했는지를 체크해서 순차적 흐름의 차이
동기 : 요청자가 요청받은 함수의 작업이 완료되었는지 계속 확인 (여러 함수들이 시간을 맞춰 실행됨)
비동기 : 요청자는 요청후 신경X, 요청받은 함수가 작업을 마치면 알려줌 (함수들의 작업 시작/종료 시간이 맞지 않을수도)
출처: 인파 님 티스토리
🤔정리하면서 대부분 퍼왔던데, 직접 보려고 만들었나?
💡그렇습니다. 까먹기가 일상인 저에게 꼭 필요한 내용들만 퍼와서 정리했습니다.
🤔그렇다면 정말 하나도 빠짐없이 모든 정리가 되었나요?
💡솔직히 V8엔진이나 libuv 등의 정리는 심오하게까지 하지는 못했습니다. 일단 개념을 먼저알아야 탐구가 가능할 것 같은 분량이라...
🤔기존에 공부하던 Java의 SpringFramework 와 차이점을 느꼈는지?
💡어리석게도 이 질문을 검색을 했는데, 아주 명확하게 해답이 있었다.
Node.js는 Runtime 환경이고, Spring은 Framework이다. 따라서 Runtime 환경과 Framework를 비교하는 것은 맞지 않을 수 있다.
내가 공부를 하고 느낀건 Single Thread 와 Multi Thread 의 차이점...?
그리고 Java 와 JavaScript 차이라고 생각이 든다.
node.js 의 구조를 조금은 공부하고 나니 뭔가 이제는 코드를 짜보고 싶은 생각이 든다.
처음에 SpringFramework 를 공부할때도 프레임워크의 개념부터 Spring 의 3대 요소 등 이러한 부분을 공부하는데 되게 애를 많이먹었다.
그렇지만 위의 공부덕에 이해가 좀 더 빨랐기도 했다.
차근차근 준비해서 사이드 프로젝트를 하나 해보며 준비를 더 해볼 생각이다.