목차
1. 서버
2. javascript Runtime
3. 이벤트 기반
4. 논 블로킹 I/O
5. 싱글 스레드
6. 서버로서의 노드
7. 서버 외의 노드
1. 서버
서버(Server)란 네트워크 상에서 클라이언트의 요청을 받아 응답을 제공하는 프로그램을 뜻합니다.
예를 들어, 브라우저 주소창에 www.google.co.kr 를 입력하고 엔터를 치면, 구글 페이지가 브라우저 화면에 나타나게 되죠?
이 구글 페이지가 바로 클라이언트(사용자)의 요청에 대한 구글 서버의 응답인 것입니다.
과정을 좀 더 자세히 살펴보면...
1. 클라이언트가 브라우저에 주소를 입력하고 엔터를 치면, 브라우저는 도메인 네임 서버(DNS)에게 이 도메인(www.google.co.kr)에 대한 IP 주소를 물어봅니다. DNS는 요청받은 도메인이 모르는 도메인이라면, 상위 레벨의 DNS에게 다시 물어보게 되고, IP 주소를 알아내면 아래의 DNS에게 IP 주소를 전달합니다. 이 과정을 Recursive Query라고 합니다.
2. IP 주소를 알아낸 브라우저는 이 IP 주소, 즉 서버와 HTTP Connection 과정(3-Way-Handshake)을 거친 후, 클라이언트(사용자)의 요청을 전달합니다.
3. 요청을 받은 서버는, 요청에 따른 응답을 준비해서 클라이언트에게 응답해줍니다. PHP로 예를 들면, HTML 문서에 PHP 문법 요소가 들어 있으면 서버가 PHP 엔진에게 해독을 요청하고, PHP 엔진은 코드에 DB 쿼리 요소가 있으면, DB 엔진에게 DB 자원을 요청해서 최종적으로 HTML 문서를 만들고, 이를 클라이언트 측에 응답하게 되는 것입니다.
이 응답은 반드시 200 OK와 같은 응답 뿐만 아니라, 상황에 따라 다른 응답을 보낼 수도 있습니다. 응답 코드 종류에 대해 궁금하시다면 아래의 링크를 참고!
https://namu.wiki/w/HTTP/%EC%9D%91%EB%8B%B5%20%EC%BD%94%EB%93%9C
4. 요청과 응답이 완료되었으므로, 브라우저와 서버는 다시 한번 3-Way-Handshke 과정을 거쳐 연결을 종료하고, 브라우저는 응답받은 HTML을 화면에 렌더링해서 최종적으로 사용자에게 보여지게 됩니다.
또한, 서버는 다른 서버에 요청을 보내기도 합니다.
예를 들어, A 서버가 B 서버에게 요청을 보내는 경우엔, A 서버는 클라이언트가 됩니다.
2. javascript Runtime
Node.js는 Chrome V8 javascript 엔진으로 빌드된 javascript Runtime입니다.
쉽게 말해서, javascript 실행 환경인 것이죠.
기존에는 javascript를 실행하려면, 브라우저에 내장된 javascript 엔진으로만 실행했어야 했습니다.
추후에 브라우저 외의 환경에서 javascript를 실행하려는 노력이 있었으나, 성능이 좋지 않아서 호응이 적었습니다.
하지만, 2008년 구글이 V8이라는 javascript 엔진을 내장한 크롬을 출시했고, 다른 javascript 엔진과는 확연히 다른 성능을 보여주었습니다.
구글은 이를 오픈 소스로 공개했고, 2009년에 V8 엔진 기반의 노드 프로젝트를 시작하게 되었습니다.
노드의 내부 구조는 C, C++로 작성된 V8 엔진과 라이브러리들로 구성되어 있습니다.
특히 libuv 라이브러리는 노드의 특성인 이벤트 기반, 블로킹 I/O 모델을 구현하고 있습니다.
3. 이벤트 기반(Event-driven)
javascript는 이벤트 기반으로 동작합니다.
이벤트(Event)란 사용자의 클릭이 될 수도 있고, 네트워크 요청 등의 어떠한 행위가 될 수도 있습니다.
이런 이벤트들을 미리 정의하고 등록해 두어서 이벤트를 처리하는 방식을 이벤트 기반(Event-driven)이라고 합니다.
이벤트들을 등록한다는 말은, 이벤트 리스너(Event-listener)에 콜백(callback) 함수를 등록한다는 의미입니다.
즉, 어떠한 이벤트가 발생하면 이벤트 리스너에 등록된 콜백 함수를 호출해서 이벤트에 대한 처리를 할 수 있게 되는 것입니다.
이벤트를 처리하면, 다음 이벤트가 발생할 때까지 대기합니다.
여러 이벤트의 콜백 함수가 동시에 호출되면, 이벤트 루프(Event-loop)는 이 함수들을 호출 스택(Call-stack)이라는 공간에 쌓아두고 후입선출(LIFO) 순서로 함수를 실행합니다.
예를 들어, 아래와 같은 코드가 있다고 가정해 봅시다.
function first() {
second();
console.log("첫 번째");
}
function second() {
third();
console.log("두 번째");
}
function third() {
console.log("세 번째");
}
first();
위의 코드를 실행하면 아래와 같은 결과가 나옵니다.
이유는 first(), second(), third()의 순서로 함수가 호출 스택에 쌓였고, 이를 LIFO 순서로 실행했기 때문입니다.
(anonymous 함수는 처음 실행 시의 전역 컨텍스트로서, 함수가 호출되었을 때 생성되는 환경을 의미합니다.)
third() |
second() |
first() |
anonymous |
그런데 이상합니다. console.log()도 함수니까 아래처럼 쌓여야 하지 않을까요?
console.log("첫 번째") |
console.log("두 번째") |
console.log("세 번째") |
third() |
second() |
first() |
anonymous |
호출 스택의 실행은 LIFO 순서가 맞지만, "}"과 같이 함수가 종료되는 시점에서 해당 함수는 호출 스택에서 즉시 실행되어 사라지므로 세 번째, 두 번째, 첫 번째의 순서로 콘솔에 출력된 것입니다.
그렇다면 아래와 같은 코드는 어떻게 실행될까요?
function run() {
console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
위의 코드를 실행하면 아래와 같은 결과가 나옵니다.
이렇게 실행되는 이유를 알기 위해선 내부 구조를 알아야 하는데, 내부 구조는 아래 사진과 같습니다.
1) JS 엔진의 Heap : 변수, 함수, 호출 등을 처리하는 메모리 공간
2) JS 엔진의 Stack : 함수들을 후입선출 방식으로 처리하는 공간
3) WebAPIs : setTimeout과 같은 타이머나 이벤트 리스너들이 대기하는 공간, 여러 작업이 동시에 실행될 수 있음
4) Callback Queue : WebAPIs 내 리스너의 이벤트가 발생했을 때, 콜백 함수를 WebAPIs로부터 전달받아 선입선출 방식으로 처리하는 공간
5) Event Loop : 콜백 큐의 함수를 콜 스택으로 올리는 역할
위처럼, console.log 함수가 먼저 실행되고, setTimeout 함수는 WebAPIs로 이동되어 3초 후 콜백 큐로 이동되고, 이벤트 루프에 의해 콜 스택으로 이동되어 처리되었기 때문에 위와 같은 결과가 나온 것을 알 수 있습니다.
4. 논 블로킹 I/O
이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있습니다.
작업을 처리하는 방법에는 논 블로킹과 블로킹 방법이 있습니다.
1) 논 블로킹 : 비동기 방식으로, 이전 작업이 끝나지 않더라도 다음 작업을 실행
2) 블로킹 : 동기 방식으로, 이전 작업이 끝나지 않으면 다음 작업을 실행하지 않음
아래 예제는 블로킹 방법을 사용한 코드입니다.
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
longRunningTask();
console.log('다음 작업');
이번엔 오래걸리는 작업을 WebAPIs로 보내놓고 다음 작업을 처리하게끔 논 블로킹 방법으로 바꾸어 보겠습니다.
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음 작업');
위와 같이 논 블로킹 방법으로 코드를 작성하더라도, 실행 순서만 바꿔준 것이기 때문에 대부분 전체 처리 시간에는 영향을 주지 않습니다.
하지만, I/O 처리와 같은 동시에 처리할 수 있는 일부 작업에 논 블로킹 방법을 사용하면 좀 더 효율적으로 처리할 수 있고,
오래 걸리는 작업을 뒤로 미루고, 간단한 작업을 먼저 처리하게 끔 할 수도 있는 데 의의가 있습니다.
5. 싱글 스레드
javascript는 싱글 스레드 언어이고, javascript가 코드를 동시에 실행시킬 수 없는 이유이기도 합니다.
스레드를 이해하기 위해선, 프로세스를 이해해야 합니다.
1) 운영체제는 작업 단위인 여러 프로세스들을 가집니다. 프로세스는 노드, 브라우저 같은 프로그램입니다.
2) 프로세스는 흐름 단위인 여러 스레드들을 가집니다. 스레드를 여러 개 생성해 작업을 동시에 처리할 수 있습니다.
노드는 싱글 스레드라고 많이 불리는데, 사실 멀티 스레드입니다.
하지만, 개발자가 직접 제어할 수 있는 스레드는 하나이기 때문에 싱글 스레드라 불리는 것입니다.
노드가 싱글 스레드로 멀티 프로세싱을 채택한 이유는 아래와 같습니다.
1) 멀티 스레딩보다 개발이 쉬움
2) 멀티 스레딩보다 I/O 작업에서 효율적임
3) 멀티 스레딩에서 발생하는 동시성 문제를 고려하지 않아도 됨
6. 서버로서의 노드
노드는 기본적으로 싱글 스레드, 논 블로킹 모델을 사용하므로, 노드를 서버로 사용했을 때 장단점은 싱글 스레드, 논 블로킹 모델의 장단점과 크게 다르지 않습니다.
장점 | 단점 |
멀티 스레드 방식에 비해 적은 컴퓨터 자원 사용 | 기본적으로 싱글 스레드라서 CPU 코어를 하나만 사용 |
I/O 작업이 많은 서버로 적합 | CPU 작업이 많은 서버로는 부적합 |
멀티 스레드 방식보다 개발이 쉬움 | 하나뿐인 스레드가 멈추지 않도록 관리가 필요함 |
웹 서버가 내장되어 있음 | 서버의 규모가 커졌을 때 관리하기 힘듦 |
javascript를 사용함 | 어중간한 성능 |
JSON 형식과 쉽게 호환됨 |
때문에, 노드를 서버로 사용한다면 작은 데이터를 실시간으로 주고받는 서버를 개발할 때 적합합니다.
채팅, 주식차트, JSON 데이터를 제공하는 API 서버를 예로 들 수 있습니다.
7. 서버 외의 노드
처음에는 노드를 대부분 서버로 사용했지만, javascript runtime이기 때문에 서버에 한정되지 않고 웹, 모바일, 데스크톱 애플리케이션 개발에도 사용되기 시작했습니다.
1) 대표적인 웹 프레임워크로는 앵귤러, 리액트, 뷰 등이 있습니다.
2) 모바일 개발 도구로는 리액트 네이티브를 많이 사용합니다.
3) 데스크톱 개발 도구로는 일렉트론이 대표적이고, 일렉트론을 사용해 만들어진 개발 도구로는 Atom, Slack, Discord, Visual Studio Code 등이 있습니다.
'JavaScript > NodeJS' 카테고리의 다른 글
Mac) node.js 버전 변경 (1) | 2023.07.09 |
---|---|
Express CORS 설정 (1) | 2023.01.18 |
"Node.js 교과서" 정리 노트 - 2장, 알아두어야 할 자바스크립트 (0) | 2021.11.16 |
Rocky Linux에 node.js 설치하기 (0) | 2021.11.16 |