Node.js에 대해 알아보고 사용을 위한 세팅 과정을 소개해 보도록 하겠다.
박승규 저자의 Node.js 백엔드 개발자 되기 책의 내용을 참고하였다.
Node.js란?
- Chrome V8 JavaScript 엔진으로 빌드된 오픈 소스 서버 사이드 JavaScript 런타임이다. 노드를 통해 다양한 자바스크립트 애플리케이션을 실행할 수 있으며, 서버를 실행하는데 가장 많이 사용된다.
- Ryan Dahl에 의해 개발되었으며 2009년 처음 공개됨.
- 기존의 웹 개발 방식을 많이 바꾸고 새로운 개발 환경과 패러다임을 선보임.
- Node.js는 확장성이 있는 네트워크 애플리케이션 개발에 사용되는 소프트웨어 플랫폼.
- JavaScript를 언어로써 사용하며, Non-blocking I/O와 단일 스레드 이벤트 루프를 통한 높은 처리 성능을 가진다는 특징이 있음.
- 내장 HTTP 서버 라이브러리를 포함하고 있어 웹 서버에서 Apache와 같은 별도 소프트웨어 없이 동작하는 것이 가능하며, 이를 통한 웹서버의 동작에 있어 많은 통제에서 벗어나 여러 기능을 가능하게 함.
- Node.js를 통해 실시간 기능이나 로그인 기능, 데이터베이스 기능 등을 웹에 넣을 수 있음.
- 비동기 I/O 처리 방식을 채택하여 입출력 작업이나 네트워크 요청과 같은 작업을 블로킹하지 않고 비동기로 처리함. (여러 작업을 동시에 처리하면서 더 효율적인 서버를 구성할 수 있음)
- 단일 쓰레드 기반으로 동작하며, 이벤트 루프와 함꼐 이벤트 기반 모델을 사용. 이벤트 루프는 이벤트를 비동기적으로 처리하고, 작업이 완료되면 콜백을 호출하여 결과를 반환.
- V8 Javascript 엔진을 사용하여 빠른 속도를 제공. V8은 구글 Chrome 브라우저에서 사용되는 엔진으로, 높은 성능과 최적화된 실행을 지원함.
- Node.js는 많은 개발자들이 사용하고 있으며, NPM(Node Package Manager)을 통해 수많은 오픈소스 라이브러리와 모듈을 제공받을 수 있음. (개발시간 단축, 개발 효율성 향상)
- 비동기 I/O 처리와 이벤트 기반 모델로 인해 Node.js는 높은 확장성을 제공함. 작은 규모부터 큰 규모까지 서버를 구성하고 확장하기 쉬움.
▷ Node.js 핵심 키워드
- 구글 V8 JavaScript Engine
- 고성능 네트워크 서버
- Single Thread(단일 쓰레드) Event Loop 기반
- Non-Blocking I/O (비동기 I/O 처리)
- JavaScript
- 개발 생산성 향상
- 방대한 모듈 제공 (NPM)
▷ Node.js 구성요소
- Node.js의 소스 코드는 JavaScript와 C++, Python 등으로 이루어져 있음.
구성요소 | 설명 |
Node.js API (JavaScript) | 자바스크립트 API |
Node.js 바인딩 | 자바스크립트에서 C/C++ 함수를 호출할 수 있게 함 |
Node.js 표준 라이브러리 (C++) | 운영체제와 관련된 함수들 타이머(setTimeout), 파일시스템(filesystem), 네트워크 요청(HTTP) |
C/C++ 애드온 | Node.js에서 C/C++ 소스를 실행할 수 있게 하는 애드온 |
V8 (C++) | 오픈 소스 자바스크립트 엔진 자바스크립트를 파싱, 인터프리터, 컴파일, 최적화에 사용됨 |
libuv (C++) | 비동기 I/O에 초점을 맞춘 멀티플랫폼을 지원하는 라이브러리 이벤트 루프, 스레드 풀 등을 사용함 |
기타 C/C++ 컴포넌트 | c-ares(DNS), HTTP Parser, OpenSSL, zlib |
↳ V8은 자바스크립트 코드를 실행하도록 해주고, libuv는 이벤트 루프 및 운영체제 계층 기능을 사용하도록 API를 제공함.
▶ V8 엔진
- C++로 만든 오픈 소스 자바스크립트 엔진(= 사용자가 작성한 코드를 실행하는 프로그램).
- 엔진은 파서, 컴파일러, 인터프리터, 가비지 컬렉터, 콜 스텍(현재 실행중인 서브루틴에 관한 정보를 저장하는 스택), 힙으로 구성됨.
- 인터프리터 역할을 하는 이그니션과 컴파일러 역할을 하는 터보팬을 사용하여 컴파일함.
- js 코드는 (1)파서에 전달되어 (2)추상 구문 트리로 만들어짐. 이후 (3)이그니션 인터프리터에게 전달되면, 이그니션은 추상 구문 트리를 (4)바이트 코드로 만듦. (5)최적화가 필요한 경우이면 터보팬으로 넘김. 그러면 터보팬에서 컴파일 과정을 걸쳐서 (6)바이너리 코드가 됨. 최적화가 잘 안된 경우는 (7)다시 최적화를 해제하고 이그니션의 인터프리터 기능을 사용함.
- 인터프리터와 컴파일러의 장점을 동시에 가지고 있는 프로그램을 JIT(Just-in time) 컴파일러라고 함. → 속도가 빠르며 적재적소에 최적화할 수 있지만, 컴파일러와 인터프리터가 동시에 실행되어 메모리를 더 많이 씀
이그니션 인터프리터 | 매우 빠른 초기화 매우 컴팩트한 바이트 코드 생성 자주 실행되지 않는 코드에 적합 |
터보팬 컴파일러 | 매우 빠른 기계어 생성 최적화 제공 자주 실행되는 코드에 적합 |
▶ libuv
- 이벤트 루프와 운영체제 단 비동기 API 및 스레드 풀을 지원하는 라이브러리.
- HTTP, 파일, 소켓 통신 I/O 기능 등 JavaScript에 없는 기능을 제공하는데 이유는 C++ 라이브러리를 사용함에 있음. (js 언어에서 C++ 코드 실행 가능하게 해둠. js로 C++ 코드를 감싸서 사용하는 방식(=C++ 바인딩))
- libuv는 다양한 플랫폼에서 사용가능한 이벤트 루프 제공. 또한 네트워크, 파일 I/O, DNS, 쓰레드 풀 기능 제공.
▷ Node.js 애플리케이션 코드 실행 프로세스
1. 애플리케이션에서 요청 발생. V8 엔진은 js 코드로 된 요청을 바이트 코드나 기계어로 변경함.
2. js로 작성된 Node.js의 API는 C++로 작성된 코드를 사용함.
3. V8 엔진은 이벤트 루프로 libuv를 사용하고 전달된 요청을 libuv 내부의 이벤트 큐에 추가함.
4. 이벤트 큐에 쌓인 요청은 이벤트 루프에 전달되고, 운영체제 커널에 비동기 처리를 맡김. 운영체제 내부적으로 비동기 처리가 힘든 경우(DB, DNS lookup, 파일 처리 등)는 워커 쓰레드에서 처리함.
5. 운영체제의 커널 또는 워커 쓰레드가 완료한 작업은 다시 이벤트 루프로 전달됨
6. 이벤트 루프에서는 콜백으로 전달된 요청에 대한 완료 처리를 하고 넘김.
7. 완료 처리된 응답을 Node.js 애플리케이션으로 전달.
▷ 싱글 스레드
- Node.js는 싱글 스레드로 구현되고 이벤트 기반 아키텍처를 구현함.
- 싱글 스레드는 콜 스택이 하나만 있음을 의미함.
- 콜 스택이 하나이므로 한번에 하나의 작업만 가능.
▷ 이벤트 기반 아키텍처
- 싱글 쓰레드로 요청을 처리하는 서버가 있을때, 하나 당 0.1초가 걸리는 요청이 동시에 100개가 오면 마지막으로 요청한 사람은 10초를 기다려야 함. 멀티 쓰레드를 지원하는 언어라면 쓰레드를 100개 만들어서 동시에 처리 가능하지만 싱글 쓰레드인 자바스크립트는 그렇게 할 수 없음.
↳ 이벤트 기반 아키텍처를 적용하여 해결 → 콜 스택에 쌓인 작업을 다른 곳에서 처리한 다음 처리가 완료되었을 때 알림을 받으면 쓰레드가 하나라도 빠르게 처리할 수 있음.
ex) 커피숍이 있을 때, 카운터에서 주문 완료시 주문은 제조를 하는 직원에게 건네짐. 카운터는 커피가 나올때까지 기다리지 않고 다음 고객의 주문을 받음. 진동벨을 받은 고객은 진동벨이 울릴때까지 기다렸다가 울리면 주문한 음료를 받아감. 이때 줄을 섰던 순서와는 다르게 빠르게 제조된 음료가 먼저 나올 수 있음.
> Node.js의 이벤트 기반 아키텍처 프로세스
1. 자바스크립트 코드는 V8의 콜 스택에 쌓이고 I/O 처리가 필요한 코드는 이벤트 루프로 보내게 됨.
2. 이벤트 루프에서는 말 그대로 루프를 실행하면서 운영체제 또는 쓰레드 워커에 I/O 처리를 맡기게 됨.
3. 쓰레드 워커와 운영체제는 받은 요청에 대한 결과를 이벤트 루프로 돌려줌.
4. 이벤트 루프에서는 결과값에 대한 코드를 콜 스택에 다시 추가함.
▷ 이벤트 루프
- Node.js에서는 이벤트 기반 아키텍처를 구축하는데 반응자 패턴(reactor pattern)을 사용함.
- 반응자 패턴은 이벤트 디멀티플렉서와 이벤트 큐로 구성됨.
- 반응자 패턴은 이벤트를 추가하는 주체와 해당 이벤트를 실행하는 주체를 분리하는 구조.
- 반응자 패턴에서 이벤트 루프는 필수이며, Node.js의 이벤트 루프는 libuv에 있음.
- 각 운영체제의 계층을 추상화한 기능을 제공함.
- 이벤트 루프는 여러개의 FIFO 큐로 이루어져 있으며, 각 단계를 돌면서 각 큐에 쌓인 이벤트를 모두 처리함.
> Node.js 이벤트 루프의 흐름
1. 이벤트 루프의 시작 및 각 반복의 마지막에 루프가 활성화 상태인지 체크
2. 타이머 단계에서는 타이머 큐(timer queue)를 처리함. (setTimeout(), setInterval() 등 처리)
3. 펜딩 I/O 콜백 단계에서는 다음 반복으로 연기된 콜백을 처리함.
4. 유휴, 준비 단계는 내부적으로만 사용됨.
5. 폴(Poll) 단계에서는 새로운 연결(소켓 등)을 맺고, 파일 읽기 등의 작업을 함. 각 작업은 비동기 I/O를 사용하거나 스레드 풀을 사용.
6. 검사 단계에서는 setImmediate()를 처리
7. 종료 콜백 단계에서는 콜백의 종료 처리(파일 디스크립터 닫기 등)를 함.
Node.js 사용 이유
⦁ 높은 성능
- Node.js는 비동기 I/O 처리와 이벤트 기반 모델을 사용하여 높은 성능을 제공함. 비동기 처리로 인해 블로킹되는 일이 없으며, 작은 규모부터 대규모까지 확장 가능한 서버를 구축할 수 있음
⦁ 빠른 개발
- JavaScript를 사용하여 서버 사이드와 클라이언트 사이드 양쪽에서 개발할 수 있기 때문에 개발 생산성이 높아짐. 또한, NPM(= Node.js의 패키지 관리자)을 통해 다양한 모듈과 라이브러리 쉽게 활용 가능.
⦁ 단순하고 가볍다
- Node.js는 비교적 간단하고 가벼운 구조를 가지고 있어 배우기 쉽고 빠르게 개발을 시작할 수 있음.
⦁ 큰 커뮤니티와 생태계
- Node.js는 많은 개발자들이 사용하고 있으며, NPM을 통해 수많은 오픈소스 라이브러리와 모듈이 제공되어 있음. 이로인해 개발자들은 기능을 쉽게 확장하고 개발시간을 단축할 수 있음.
⦁ 재사용 가능한 코드
- 클라이언트와 서버에서 동일한 언어를 사용하므로 코드를 재사용할 수 있음. 또한 JavaScript의 JSON 형식을 사용하여 데이터를 교환하므로 데이터 처리가 간편함.
⦁ 확장성
- Node.js는 많은 동시 접속을 처리할 수 있는 높은 확장성을 가짐. 이로인해 대규모 애플리케이션에서도 우수한 성능을 유지할 수 있음.
⦁ 크로스 플랫폼
- Node.js는 크로스 플랫폼으로 지원되며, Windows, macOS, Linux 등 다양한 운영체제에서 실행할 수 있음.
↳ 이러한 이유들로 Node.js는 웹 애플리케이션, RESTful API, 실시간 서버, 웹 소켓 등 다양한 서버 사이드 애플리케이션을 개발하는데 사용됨
Node.js의 장단점
장점 | 단점 |
- 비동기 이벤트 기반 I/O를 사용해 동시에 여러 요청을 다루기 쉬움 - 자바스크립트를 사용해 프론트엔드 개발자의 백엔드 진입이 용이함 - 클라이언트와 같은 언어를 사용하면 서버의 코드에 사용된 로직을 클라이언트에서도 사용할 수 있음 - 개발자 생태계가 잘 구성되어 있어서, 패키지 매니저에서 필요한 대부분을 제공함. - V8 엔진이 JIT 컴파일러이므로 서버 기동이 빠름 - CPU 사용량이 많은 작업을 하는 서버가 아니라면, 굉장히 적은 메모리로 아주 좋은 성능을 낼 수 있음. |
- 기본적으로는 CPU를 하나만 사용하므로 멀티코어를 사용하려면 별도의 작업이 필요함 - 비동기를 지원하지 않는 I/O 요청이나 CPU 작업은 주의해서 작업해야 함 - 콜백을 중첩해서 계속 사용하면 코드 작성 및 디버깅이 힘들어짐 - 이벤트 기반으로 프로그래밍을 해본 적이 없다면 코드 작성이 타 언어에 비해 상대적으로 어려울 수 있음. - 랭킹이나 매칭 등 CPU를 많이 사용해야 하는 서비스에는 Node.js가 적합하지 않음 |
Node.js 사용을 위한 세팅
virtual box와 Ubuntu를 사용하여 리눅스 환경에서 Node.js를 쓰도록 할 것이다.
리눅스는 Window와 달리 모든것이 파일구조이다.
> 리눅스 환경에서 하는 이유?
⦁ 비동기 I/O 처리 : Node.js는 비동기 I/O 처리를 기본적으로 지원. 리눅스는 네트워크와 파일 I/O 작업에서 비동기 처리에 뛰어난 성능을 보여줌. 이로인해 Node.js와 리눅스가 함께 사용되면 높은 성능과 효율성을 기대할 수 있음.
⦁ 확장성과 성능 : Node.js는 이벤트 루프와 단일 쓰레드 기반으로 동작하는데, 리눅스는 다중 쓰레딩 및 프로세스 지원 등으로 확장성과 성능을 보여줌. Node.js와 리눅스의 조합은 많은 동시 접속과 작업 처리를 효과적으로 다루는데 유리함.
⦁ 오픈 소스와 커뮤니티 : Node.js와 리눅스는 모두 오픈 소스로 개발되었고, 각각 뛰어난 커뮤니티를 가짐. 이들의 협력으로 서버 사이드 개발에 필요한 다양한 라이브러리와 모듈을 쉽게 얻을 수 있음.
⦁ 저렴한 서버 비용 : 리눅스 기반 서버는 라이센스 비용이 없으며, 하드웨어 요구사항이 낮기 때문에 저렴하게 서버를 구성할 수 있음. Node.js와 리눅스는 저렴하고 경제적인 서버 인프라 구축에 적합함.
⦁ 개발 환경 통일성 : 개발 환경의 통일성은 팀원들 간의 협업과 개발 생산성을 높이는데 도움이 됨. Node.js와 리눅스는 둘 다 JavaScript와 관련이 있는 기술로(리눅스는 직접적인 관련은 없지만 Node.js, 웹 브라우저, 웹 개발 시 사용), 백엔드와 프론트엔드 개발 모두 JavaScript로 구현할 수 있어 개발 환경의 통일성을 유지할 수 있음.
⦁ 배포 용이성 : Node.js는 패키지 관리자인 NPM을 통해 다양한 모듈을 사용할 수 있고, 리눅스는 컨테이너 기술인 Docker와 같은 배포 도구와 잘 통합됨. 이처럼 Node.js와 리눅스를 함께 사용하면 애플리케이션 배포가 편리해짐.
우선, 리눅스 운영체제를 윈도우 위에서 실행할 수 있게 하는 Oracle의 VirtualBox을 설치해 주도록 한다. 데스크톱 가상화 소프트웨어이며, 컴퓨터 내에서 가상 머신을 생성하여 여러 운영체제를 동시에 실행하고 격리된 환경에서 작업할 수 있게 해주는 도구이다. 필자는 VMware를 가상화 도구로써 리눅스 공부할 때 써봤는데, VMware는 상용 라이센스를 가지고 VirtualBox는 오픈 소스라는 차이가 있는듯 하다.
https://www.oracle.com/kr/virtualization/technologies/vm/downloads/virtualbox-downloads.html
(Virtual Box 다운로드 링크)
다음은 리눅스 운영체제인 Ubuntu를 다운로드 받아준다.
우분투 홈페이지에 들어가서 Ubuntu LTS를 다운받아준다.
다운받게되면 디스크 이미지 파일인 ubuntu...iso 파일이 받아진다. (iso파일은 운영체제와 소프트웨어 배포하는데 쓰이는 확장자인듯. iso 파일은 Virtual Machine 소프트웨어를 사용하여 가상 머신에 설치하거나, USB 부팅 디스크로 만들어 부팅 가능한 USB 드라이브를 생성하는 용도로 쓰임)
Virtual Box를 실행시켜보자
새로만들기를 눌러 이름을 지정해주고 설치한 Ubuntu의 iso파일을 지정해준다.
계정 설정을 해주고
기본메모리와 프로세서 수 세팅, 디스크 사이즈를 정해준다.
(사용하다보면 30GB 정도는 금방 먹는다 함)
세팅이 끝나고 Finish를 하면 자동으로 실우분투가 실행되고 설치를 진행한다.
설치가 완료되면 사용가능 상태가 된다.
초기 화면은 너무 작으므로 화면 비율 세팅을 적당하게 해준다.
몇가지 세팅을 위해 좌측 하단 show applications에 들어가 Terminal 창을 띄워야 하는데 눌렀을 때 아래처럼 뜨다가 실행은 안되는 경우가 생길 수 있다.
이 경우는 Settings에 들어가 Region & Language를 선택한 뒤 English (United States)로 기본 설정되있는 것을 Canada로 바꾸면 해결되는 것으로 보인다.
설정 변경 후 적용을 위해 가상 머신을 Restart 해준다.
이제 터미널이 실행 된다.
다음은 리눅스 개발 환경 구축을 해보겠다.
Node.js 설치 → VS Code 설치 → Chrome 설치를 할 것이다.
먼저 curl을 설치한다.
curl은 Client URL을 의미하며, 클라이언트에서 커멘드라인이나 소스코드로 손쉽게 웹 브라우저처럼 활동할 수 있도록 해주는 기술이다. 즉, 서버와 통신할 수 있는 명령어 툴이며, 웹 개발에 매우 많이 사용되고 있는 무료 오픈소스라고 함.
curl을 받으려면 위의 명령어를 입력해야 하는데 권한이 없으므로 거부되었다.
보안상의 이유로 root로 작업하면 안되므로 suldenlion 계정에 root 권한을 빌려줄 것이다.
su (switch user) 명령으로 root로 계정 전환을 해주고, sudo vim /etc/sudoers 명령어를 입력해준다.
vim이 안깔려 있으므로 찾을 수 없다고 뜬다. vi로 실행해준다.
(sudoers 명령어는 sudo 명령을 사용할 때 사용자가 따라야하는 규칙을 정의한다)
그럼 이런 화면이 나오는데, esc를 몇번 눌러주고 # User privilege specification 이 있는 곳으로 찾아간다.
계정과 권한을 추가시켜주고 ":wq!"를 입력하여 저장 후 빠져나온다.
다시 사용하던 계정으로 돌아와서 설치를 진행한다.
설치가 되었고 PPA를 추가한다.(PPA는 Personal Package Archive의 줄임말로 우분투 공식 저장소에 없는 패키지들이 있는 저장소라고 한다)
Node.js를 설치해주고
Node.js가 잘 설치되었는지 "node --version" 명령어로 확인해본다.
그리고 NPM을 사용하기 위한 build-essential 패키지도 설치한다.
Node.js를 사용할 준비는 완료되었고, 다음은 VS Code와 Chrome을 설치하겠다.
메뉴에 있는 FireFox 웹 브라우저를 켠 후, "https://code.visualstudio.com" 주소로 들어가준다.
.deb 확장자(Debian)를 가진 파일을 다운받는다.
다운로드 후 download 위치로 가면 deb파일이 있는것을 확인할 수 있다.
파일 우클릭 후 Open With Other Application을 누른다.
Software Install을 선택하고 Select를 누른다.
그럼 아래의 창이 나올건데 Install을 누르고 계정 비밀번호를 눌러주면 설치된다.
메뉴를 눌러서 보면 VS Code가 추가된 것을 확인할 수 있다.
같은 방식으로 Chrome 설치도 진행해준다.
"hello Node.js"를 출력하는 Node.js 프로그램을 만들어보고 마무리하겠다.
VS Code를 켜서 다음과 같은 코드를 쳐준다.
const http = require("http");
모듈을 읽어오는 require() 함수를 써서 http 모듈을 불러와준 후 http라는 변수를 만들어준다. (모듈명과 변수명은 다르게 해도 되지만 같게 하는것이 관행이라함)
const server = http.createServer(callback)
createServer() 메서드로 서버 인스턴스를 만든다. 인수로는 콜백 함수를 받고, 콜백 함수에서는 http 서버로 요청이 들어오면 해당 요청을 처리할 함수를 설정함. (콜백 함수는 요청 처리에 사용할 request와 response 객체를 인수로 받음)
console.log() 에서는 전역변수로 설정한 count로 요청에 대한 로그를 남겨준다.
res.statusCode = 200;
res.statusCode는 요청에 대한 상태 코드를 듯하는데 200으로 설정한다. http 프로토콜에서 200은 성공을 의미함.
res.setHeader("Content-Type", "text/plain");
이 코드는 HTTP 요청/응답에 대한 부가정보를 설정하는 코드이다. (텍스트를 평문으로 해석하겠다는 의미)
res.write("hello\n");
응답으로 "hello"를 보내줌.
setTimeout(() => {res.end("Node.js")}, 2000);
setTimeout()은 콜백 함수와 숫자를 인수로 받으며 해당 숫자(밀리초) 만큼의 시간이 지나면 콜백 함수를 실행한다.
2초 후 "Node.js"를 응답으로 주고 http connection을 끝내는 동작을 한다. (setTimeout() 함수는 libuv에서 제공하는 타이머를 사용함)
server.listen(8000, () => console.log("Hello Node.js"));
server.listen(8000)으로 사용할 포트 번호를 8000번으로 지정한다. 또한 IP가 생략되었으므로 기본값이 localhost나 127.0.0.1이 된다.
로컬호스트 8000으로 들어가면 저렇게 찍혀나오고 콘솔에는 count값이 아래와 같이 찍혀나온다.
웹 페이지 띄울때 setTimeout() 한것이 한번에 출력되므로 확인하기 어렵다.
VS Code에서 Terminal을 하나 띄워서 확인해보겠다.
New Terminal로 터미널 창을 띄워주고 curl "http://localhost:8000"을 실행해주면 hello가 찍히고 2초 뒤에 Node.js가 찍히는것을 확인할 수 있다.
'Node.js' 카테고리의 다른 글
댓글 기능이 있는 게시판 만들기 (Node.js, MongoDB, Express 사용) (0) | 2023.07.31 |
---|---|
MongoDB에 대하여 (0) | 2023.07.30 |
Node.js & 익스프레스 사용 정리 (WAS 구현) (0) | 2023.07.27 |
자바스크립트 비동기 처리 정리 (0) | 2023.07.26 |
댓글