NodeJS 스터디 1주차 정리

부산에서 매주 진행되는 노드 입문 강좌 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

이 강좌는 제로초님의 인프런 강좌를 보고 요약해놓은 내용입니다.

  • 챕터 2 - ES2018
  • 챕터 3 - 노드 기능 알아보기

const & let

const

const 는 객체가 할당된 경우 변경이 불가능하지만 오브젝트에 대한 내부 값은 변경가능하다.

const item = {a: 1, b: 2, c: 3};

item.a = 3;

item => {a: 3, b: 2, c: 3}

let

구문적인 변수 영역 규칙을 지원한다.

{} 블록내에 변수 영역만 처리된다.

예를 들어 보자.

var 로 할 경우 밖의 변수까지 변경되는 걸 볼수 있다.

var test = "test";

if (test) {
    var test = "test2";
    console.log('블록내 : ',test);
}

console.log('블록밖', test);

.....
블록내 : test2
블록밖 test2

하지만 let 으로 할 경우 글로벌 변수의 값을 보호할 수 있다.

var test = "test";

if (test) {
    let test = "test2";
    console.log('블록내 : ',test);
}

console.log('블록밖 : ', test);

......
블록내 :  test2
블록밖 :  test

템플릿 문자열

템플릿 문자열은 문자열 연결 대신 사용할 수 있다. 중간에 변수를 삽입할수 있다.

${변수}, ` 로 감싼다.

console.log(test + " " + test2); //기존 방식

console.log(`${test} ${test2}); //템플릿 문자열

객체 리터럴

화살표 함수 (중요)

화살표를 사용하면 모든 함수 정의를 한줄로 끝낼수 있다. function 키워드를 없앴고, 화살표가 어떤 값을 반환하는지 지정해주기 때문에 return 도 없다. 만약 파라미터가 하나만 받는 경우 괄호도 생략이 가능하다.

화살표 함수는 this를 새로 바인딩 하지 않는다. 예를 들어 다음 코드에서 this는 city객체가 아니다.

const city = {
    areas: ["서울","부산"],
    print: function(delay = 1000) {
        setTimeout(function() {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

....error
Cannot read property 'join' of undefined

이 경우 this는 window 객체이기 때문에 city 는 undefined 이다.

화살표 함수는 함수 내부의 this를 외부의 this와 같게 만든다.

const city = {
    areas: ["서울","부산"],
    print: (delay = 1000) => {
        setTimeout( () => {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

....error
Cannot read property 'join' of undefined

위 코드에서 this도 결국에는 window 의 객체이기 때문에 오류가 난다.

그래서 상단의 print 함수를 function 으로 만들어주면 오류가 발생되지 않는다.

const city = {
    areas: ["서울","부산"],
    print: function(delay = 1000){
        setTimeout( () => {
            console.log(this.areas.join(','))
        }, delay);
    }
}

city.print();

.....error
서울,부산

구조분해 ( destructuring )

구조분해를 사용하면 객체 안에 있는 필드 값을 원하는 변수에 대입할 수 있다.

비구조화 할당 시 this가 의도와 다르게 동작하는 현상이 있을수 있는 점 유의하자.

var city = {
    area: ['busan','seoul'],
    latlng: ['123:456', '789:321']
}

const { area, latlng } = city;

console.log(area[0], latlng[0]);
...

const [ busan, seoul ] = area;

console.log( busan, seoul );

위 코드 처럼 배열도 비구조화 할당이 가능하다.

스프레드 연산자

기존 배열의 값은 변경하지 않고 새로 만들어서 리턴한다.

파라미터 갯수를 동적으로 받을수 있다.

const x = (a, ...b) => console.log(a,b);

x(1,2,3,4,5);

........
1 [ 2, 3, 4, 5 ]

Promise

비동기적인 동작을 잘 다루기 위한 방법 중 하나이다.

const promise = new Promise((resolve, reject) => {
    const a = 1;
    const b = 1;
    if(a + b <= 2) {
        resolve(a + b);
    } else {
        reject(a + b);
    }
});

promise
    .then(( success ) => {console.log(success)})
    .catch((error) => {console.log(error)});


.... 성공
> 2

또한 여러개의 then으로 사용가능하다. 콜백안에 다시 콜백이 있을 경우 여러개의 then 으로 이어진다. 그러기 위해선 resolve로 리턴을 해야줘야 한다.

const promise = new Promise((resolve, reject) => {
    const a = 1;
    const b = 1;
    if(a + b <= 2) {
        resolve(a + b);
    } else {
        reject(a + b);
    }
});

promise
    .then(( success ) => {
        return new Promise((resolve, reject) => {
            resolve("result : " + success);
        })
    })
    .then((success2) => console.log(success2))
    .catch((error) => {console.log("reject:" + error)});

....
result : 2

Promise API

무조건 성공 또는 실패 하는 경우일 경우 더 줄여서 가능하다.

const success = Promise.resolve('success');    
const failed = Promise.reject('failed');

Promise 함수들을 모아서 하나의 Promise 로 실행할수 있다.

전부 성공시 success 로 넘어가지만 한개라도 실패시 catch 로 들어가는 점 유의하자.

Promise.all([promise, success])
    .then((results) => {
        console.log(results);
    })
    .catch((error) => {
        console.log(error);
    })

Async / Await

Async 도 결국 내부에는 Promise 기반이므로 Promise를 꼭 습득하고 가자.

Promise의 단점은 결국 콜백함수내에서 이루어지기 때문에 외부 코드들과 연동성이 비동기로 이루어진다. 그래서 그걸 극복하게 동기형으로 바꿔주는 게 Async/Await 이다.

check = async () => {
    console.log('await result: ',await promise);
}

check();

오류 처리는 try / catch 문으로 한다.

check = async () => {
    try {
        console.log('await result: ',await promise);
    } catch(e) {
        console.error('error: ',e)
    }
}

check();

모듈

자바스크립트 모듈은 자바스크립트 파일에서 쉽게 불러와서 활용할 수 있는 재사용 가능한 코드 조각들을 말한다. 즉 쪼개서 불러와서 사용이 가능하다는 것이다.

자바스크립트는 각각의 모듈을 별도의 파일로 저장한다. 모듈을 만들고 외부에 익스포트 하는 방법에는 한 모듈에서 여러 자바스크립트 객체를 외부에 노출시키는 방법과 모듈당 하나의 자바스크립트 객체를 노출시키는 방법이 있다.

여러 객체를 외부에 노출 시키는 방법

>> helper.js

export const print = (message) => log(message, new Date())

export const log = (message, timestamp) => console.log(`${timestamp.toString()}: ${message}`)

module.exports = {
    print,
    log
}
const { print } = require('./txt_helper');

print("good");

하나의 객체를 외부에 노출 시키는 방법

const print = (message) => console.log(message, new Date())

export default print;
import print from './txt_helper_only_one.mjs';

print("good");

global

노드의 전역 객체

노드 실행환경내 전역에서 공통적으로 사용되는 객체이다.

> B.js

module.exports = () => global.message;
> C.js

const B = require('./B');

global.message = 'ok';

console.log(B());

>> ok 출력

console

console 객체네는 디버깅을 도와주는 많은 메소드가 준비되어 있다.

  • console.time('인자') & console.timeEnd('인자') - 전체 걸리는 시간을 측정해준다. (인자는 똑같아야 한다)
  • dir: 오브젝트 객체를 로깅할때 사용한다. colors 는 색상 지정, depth 는 깊이 지정
  • trace: 에러 발생시 오류 경로를 추척해서 보여준다.

setTimeout, setInterval, setImmediate

https://nodejs.org/ko/docs/guides/timers-in-node/

  • setTimeout : 일정시간 후에 콜백이 실행된다.
    • clearTimeout 로 취소 가능
  • setInterval : 일정 시간마다 콜백이 실행된다.
    • clearInterval로 취소 가능
  • setImmediate : 이벤트 루프 주기 끝에 코드를 실행한다.
    • clearImmediate로 취소 가능

__filename, __dirname

  • __filename 은 현재 파일 위치까지 위치를 리턴한다.
  • __dirname 은 디렉토리까지 위치를 리턴한다.

process

현재 실행중인 노드 프로그램 정보가 들어있다.

  • process.version : 노드 설치 버전
  • process.arch : 32비트인지 64비트인지 체크
  • process.platform : 운영체제
  • process.pid : 프로세스 아이디 ( 문제가 발생시 강제 종료시 아이디 지정 가능 )
  • process.uptime : 프로세스 실행되고 얼마나 지났는지 체크
  • process.cwd : 노드 프로그램이 실행되고 있는 위치
  • process.exePath : 노드 설치 위치
  • process.cpuUsage : cpu 사용량
  • process.exit : 프로세스 종료

OS

운영체제 관련된 모듈

  • os.cpu 의 경우는 멀티 프로세싱에서 자주 쓰인다.

Path

경로 관련된 모듈. **중요한 모듈중 하나이다. **

  • path.delimiter : 환경변수 패스 출력
  • path.sep : 경로 구분자 표시 . 윈도우는 \\ 이다.
  • path.dirname : 경로 폴더
  • path.extname : 확장자
  • path.basename : 파일 이름
  • path.parse : 구조로 분해
  • path.format : 분해된 구조를 합쳐준다.
  • path.normalize : 잘못된 경로를 자동으로 정상적으로 만들어준다.
  • path.isAbsoulte : 경로가 절대경로인지 상대경로인지 체크
  • path.join : 절대 경로 무시하고 조각나있는 경로를 합쳐준다.
  • path.resolve : 절대 경로 고려해서 합친다. 루트는 C:\

Url

도메인이 생략된 경우는 parse 를 통해서 처리

  • parse
  • format
  • searchParams : 파라미터 내용을 오브젝트 형태로 가져온다.
    • 파라미터들을 추가,수정,삭제등 할수 있다. (append,set,delete,get)

url 구조� �� �미� ��결과

querystring

  • url 모듈과 같이 사용한다.
  • url.parse ->querystring.parse 로 이용
  • stringify 는 파싱된 걸 다시 합쳐준다.

crypto

단방향 암호화(해시)
  • 비밀번호를 암호화할때 필요하다.

  • 복호화는 안된다.

  • createHash 는 비밀번호가 짧을 수록 같은 값이 나올수 있다.

  • 좀 더 나은 암호화 방식인 pbkdf2 함수를 사용

    • salt 라는 문자열을 비밀번호에 추가해서 좀 더 암호화한다.
    • salt 는 암호화된 비밀번호와 같이 저장
    • iteration 은 1초정도가 걸릴때까지 올려주면 좋음
  • 실무에선 bcrypt 등이 사용된다.

crypto.createHash('sha512').update('password').digest('base64');
양방향 암호화 (복호화 가능)
  • 암호화시 createCipher 함수 사용
  • aes-256 또는 aes-256-cbc 를 많이 사용한다.
  • 복호화시 createDecipher 함수 사용
  • key 값을 알아야 복호화를 할 수 있다.
  • update 함수 사용시 base64,utf8 인자가 암호화와 복호화시 바뀐다.

Util

  • callbackify : promise 함수를 거꾸로 callback 함수로 바꿔준다.
  • promisifycallback 함수를 promise 로 바꿔주는 유틸 함수 (중요함)
  • deprecate : 폐기되는 함수임을 지정한다.
    • 오류 메세지도 인자로 넣을 수 있다.
    • 결과에 Warning 이 나온다.

Fs (File System)

  • 비동기 방식으로 콜백으로 결과로 받는다.
  • 여러개의 콜백을 지정시 순서가 일정하지가 않다.
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
    if(err) {
        throw err;
    }

    console.log(data); //바이너리 파일로 리턴된다. 
    console.log(data.toString());
});

.........
<Buffer ed 85 8c ec 8a a4 ed 8a b8 31 0d 0a ed 85 8c ec 8a a4 ed 8a b8 32>
테스트1
테스트2
  • 동기 방식으로는 sync 방식도 제공한다.
    • readFileSync

기타 함수

  • access
  • mkdir
  • open
  • rename
  • unlink : 파일 삭제
  • rmdir : 폴더 삭제

promise

  • 노드 10에서 promise사용가능

버퍼 ( Buffer ), 스트림 ( Stream )

  • 데이터를 보낼때 일정량의 저장 공간에(버퍼) 담아서 보낸다.
  • 버퍼들의 통로를 뜻함
const fs = require('fs');
const readStream = fs.createReadStream('./readme.txt', { highWaterMark: 8});
const data = [];

readStream.on('data', (chunk) => {
    data.push(chunk);
    console.log('data', chunk, chunk.length);
});

readStream.on('end', () => {
    console.log('end', Buffer.concat(data).toString());
});

readStream.on('error', (error) => {
    console.log('error', error);
})
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8
data <Buffer ed 85 8c ec 8a a4 ed 8a> 8
data <Buffer b8 31 ed 85 8c ec 8a a4> 8
data <Buffer ed 8a b8 32 ed 85 8c ec> 8
data <Buffer 8a a4 ed 8a b8 31 ed 85> 8
data <Buffer 8c ec 8a a4 ed 8a b8 32> 8

청크별로 날라와서 end 이벤트 등록시 그 버퍼를 합쳐서 하나의 완전품으로 만든다.

WriteStream

const fs = require('fs');

const writeStream = fs.createWriteStream('./readme.txt');
writeStream.on('end', () => {
    console.log('완료')
})
writeStream.write('쓰기 완료1');
writeStream.write('두번째 줄');
writeStream.end();

Pipe

내부적으로 읽기와 쓰기를 연결해준다.

  • copy 함수가 따로 새로 만들어져서 사용가능하다.

zlib

압축을 할수 있다.

Event

미리 이벤트를 만들어 놓고 어떤 행동 발생시 콜백이 발생한다.

  • addListener
  • on : addListener 와 같지만 여러번 등록가능하다.
  • once : 한번만 실행된다.
  • listenerCount : 이벤트가 몇개 달려 있는지 갯수 확인
const EventEmitter = require('events');
const myEvent = new EventEmitter();

myEvent.addListener('visit', () => {
    console.log('welcome');    
});

myEvent.on('exit', () => {
    console.log('good bye');
});

myEvent.on('exit', () => {
    console.log('good bye please');
});

myEvent.once('special', () => {
    console.log('special event')
});

myEvent.emit('visit');
myEvent.emit('exit');
myEvent.emit('special');
myEvent.emit('special');

..........
welcome
good bye
good bye please
special event <-- 한번만 실행된걸 볼 수 있다. 

이벤트 취소

  • removeAllListeners(이름) 여러번 등록 했을 경우 다 제거
  • removeListener(이름, callback) 하나만 제거 가능하다. 어떤 걸 제거 해야 할지 알수 없으므로 콜백을 지정

예외처리

  • 노드는 싱글 쓰레드이므로 오류 발생시 전체에 영향을 끼칠수 있으므로 신중하게 처리
  • throw new Error
  • try / catch 로 처리 가능
  • async / await 도 try / catch
  • 내장 함수들은 error 발생해도 시스템이 중지되진 않는다.
  • process.on 으로 시스템 에러가 발생시 중지가 되지 않게 할 수 있다.
    • uncaughtExcepetion 으로 모든 에러를 잡을수 있다.
    • process.on 이벤트 실행이 꼭 보장되지 않는다.


이상으로 1주차 노드 스터디 내용 정리사항이었습니다. 

참석해주셔서 감사합니다. 

NodeJS Advance 2주차 스터디

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.

1주차 스터디 내용

이번 스터디에서는 노드의 성능향상에 대해서 공부하도록 한다.

  • Cluster 모드를 사용
  • Worker Threads를 사용

우선 테스트를 위해 express 를 사용해보자.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hi there');
});

app.listen(3000);

위와 같이 구성될 경우 일반적이라고 볼수 있는데 이건 그림으로 표현하자면

1543129280118

의도적 딜레이

만약 요청을 5초정도로 하는 경우 동시에 실행시 어떻게 되는지 체크 해보자.

const express = require('express');
const app = express();

function doWork(duration) {
    const start = Date.now();
    while(Date.now() - start < duration) {

    }
}

app.get('/', (req, res) => {
    doWork(5000);
    res.send('Hi there');
});

app.listen(3000);

하나만 실행했을 경우 5초가 걸릴것이다.

만약 2개를 동시에 실행시 처음 5초가 끝나고 두번째가 실행된다.

  • 2번째를 동시 실행시 8.57초가 걸리는 걸 볼수 있다.

1543129512873

이를 해결하기 위해 Cluster 라는 방법을 사용할 수 있다.

1543129640802

Cluster 동작원리

  • 처음 index.js 를 호출시 cluster manager를 통해서 cluster.fork() 를 실행
  • 그 후 다시 index.js 로 가서 worker instance를 실행한다.

1543129735477

소스로 구현해보자.

  • cluster.fork()를 통해서 다시 index.js 를 실행해서 프로세스를 새로 만든다.
const cluster = require('cluster');

if(cluster.isMaster) {
    cluster.fork();
} else {
    const express = require('express');
    const app = express();

    function doWork(duration) {
        const start = Date.now();
        while(Date.now() - start < duration) {

        }
    }

    app.get('/', (req, res) => {
        doWork(5000);
        res.send('Hi there');
    });

    app.listen(3000);

}

다중 클러스터

만약 Cluster 를 여러개 설정시 어떻게 될까?

cluster.fork();
cluster.fork();
cluster.fork();

테스트시 다른 탭에 있는 2개의 쓰레드가 빨리 도는 걸 확인할수 있다는데...잘 안된다...;;

AB 테스트

맥이나 리눅스에서 AB 테스팅할 수 있는 방법이 있다고 한다.

50번의 요청을 동시에 50번을 날리도록 실행

ab -c 50 -n 50 localhost:3000/fast

1543131006116

문제점

클러스터를 실행하더라도 한정된 자원이 있기 때문에 점점 가성비가 떨어진다고 한다.

이번 시간에는 클러스트 개념과 기본 사용법에 대해서 살펴보았다.

참석해주셔서 감사합니다.

NodeJS Advance 1주차 스터디 정리 #2

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.

이벤트 루프 내부 구조

이전 글에도 있지만 다시 내용을 살펴보자.

  • pendingTimers
  • pendingOSTasks
  • pendingOperations
// node myFile.js

const pendingTimers = [];
const pendingOSTasks = [];
const pendingOperations = [];

// New timbers, tasks, operations are recorded from myFile running
myFile.runContents();

function shouldContinue() {
    // Check one : Any pending setTimeout, setInterval, setImmediate?
    // Check two: Any pending OS tasks? (Like server listening to port)
    // Check three: Any pending long running operations? (Like fs module)
    return pendingTimers.length || pendingOSTasks.length || pendingOperations.length;
}
// Entire body executes in one 'tick'
while(shouldContinue()) {
    // 1) Node looks at pendintTimers and sees if any functions
    // are ready to be called. setTimeout, setInterval

    // 2) Node looks at pendingOSTasks and pendingOperations
    // and calls relevant callbacks

    // 3) Pause execution. Continue when ....
    // - a new pendingOSTask is done
    // - a new pendingOperation is done
    // - a timer is about to complete

    // 4) Look at pendingTimers. Call any setImmediate

    // 5) Handle any 'close' events
}

// exit back to terminal

이번글에서는 이러한 3가지 내용에 대해서 자세히 공부해보도록 한다.

노드는 싱글(단일) 쓰레드일까?

  • 이벤트 루프는 기본적으로 싱글쓰레드가 맞다.
  • 어떤 라이버러리들은 싱글쓰레드가 아니다.



1. Libuv Threads - pendingOperation

예를 들어 pbkdf2 함수를 예를 들어보자.

const crypto = require('crypto');

const start = Date.now();
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('1:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('2:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('3:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('4:', Date.now() - start);
});


.....................
4: 1050
1: 1072
3: 1089
2: 1112

보시다 시피 동시에 4개의 쓰레드가 동작이 된다. 만약 싱글쓰레드라고 가정하면 각각 1초씩 진행이 되어야 하고 총 4초가 걸려야 한다.

1542683601432

이유는 Libuv 내에서는 자체 Thread Pool 을 가지고 있다.

1542683727534

기본적으로 한개의 쓰레드풀에 4개의 쓰레드가 있으며 이건 Size를 늘리거나 줄일 수 있다.

process.env.UV_THREADPOOL_SIZE = 5

기본이 4개인걸 테스팅 해보자.

총 6번을 실행시 1~4번째까지는 1초가 걸리고 5번째 부터는 첫번째 내용이 끝나면 빈자리로 들어가는 걸 볼 수 있다.

const crypto = require('crypto');

const start = Date.now();
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('1:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('2:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('3:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('4:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('5:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('6:', Date.now() - start);
});

.......

4: 1050
1: 1072
3: 1089
2: 1112 
6: 1946 <-- 새로운 1초가 걸리는 걸 확인 할수 있다.
5: 1956

1542684077427

그러면 Thread Pool 에 대해 궁금한 점이 있을수 있다.

  • 자바스크립트를 위해 쓰레드 풀을 사용하거나 오직 nodejs 함수를 통해서 사용가능한가?

Custom JS 를 통해서 쓰레드 풀를 사용을 할수 있다.

  • 어떤 std 라이버러리 함수를 통해서 쓰레드 풀을 사용할 수 있는가?

모든 fs 모듈 함수를 통해서 사용 가능하다. 그리고 몇개의 crypto 함수들은 OS에 종속되어 있다.

  • 그럼 이벤트 루프에서 이러한 쓰레드풀이 어떻게 사용되어 지는가?

처음 글에서 봤던 이벤트 루프 함수 내 pendingOperations 으로 사용되어 진다.

2. OS's async - pendingOSTasks

1542685060681

Operation Thread 와는 다르게 운영체제에서 직접 실행하는 내용이다.

즉 쓰레드를 이용하지 않고 다른 영역에서 호출된다고 보면 된다.

여러개를 호출해도 한번에 실행되는 걸 볼수 있다.

const https = require('https');

const start = Date.now();

function doRequest() {
    https.request('https://www.google.com', res => {
        res.on('data', () => {});
        res.on('end', () => {
            console.log(Date.now() - start);
        });
    })
    .end();
}

doRequest();
doRequest();
doRequest();
doRequest();
doRequest();
doRequest();
doRequest();
doRequest();

...............
582
591
601
612
630
637
639
667

이에 대해 FAQ를 정리해보자.

  • node에서 std 라이버러리에서 OS's async 기능을 사용할 수 있나?

  • 거의 모든 네트워킹 관련된건 OS's async 기능을 이용한다.

  • 이벤트 루프내에서 이 기능이 사용되어 지는가?

  • pendingOSTasks로 사용되어 진다.

노드JS 흐름도

1542685666194

NodeJS 쓰레드 우선 순위

만약에 thread pool 과 os's async이 만날 경우 어떤 순서로 실행될까?

const https = require('https');
const crypto = require('crypto');
const fs = require('fs');

const start = Date.now();

function doRequest() {
    https.request('https://www.google.com', res => {
        res.on('data', () => {});
        res.on('end', () => {
            console.log(Date.now() - start);
        });
    })
    .end();
}

function doHash(no) {
    crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
        console.log(`Hash #${no}:`, Date.now() - start);
    });
}

#1
doRequest();

#3
fs.readFile('multitask.js', 'utf8', () => {
    console.log('FS:', Date.now() - start);
});

#2
doHash(1);
#4
doHash(2);
#5
doHash(3);
#6
doHash(4);

............

633
Hash #2: 965
FS: 966
Hash #1: 971
Hash #3: 990
Hash #4: 1010

우선 OS 가 먼저 실행되고 파일읽는게 먼저 실행되었는데 HASH 함수가 먼저 실행된 걸 볼수 있다.

  1. OS 실행 - doRequest()

  2. fs.readFile 실행

  3. ThreadPool 에 redFile 함수 등록

    1542686207545

  4. readFile 함수가 OS 단으로 빠짐 - 파일 읽어야 해서

  5. 그 쓰레드 번호에 다른 남아있는 HASH 함수가 등록됨

    1542686285333

  6. 그리고 readFile 함수가 끝나면 처리가 완료된다.

  7. 마지막으로 나머지 HASH 값이 처리가 된다.

여기까지 이번 주차에서 공부한 내용이다.

중요한 점은 다음과 같다.

  1. OS's async
  2. Thread Pool
  3. Thread Pool size 조절 가능함
  4. fs 사용시 순서 고려해야함

이상으로 1주차 노드 중급 스터디 정리 내용입니다.

참석해주셔서 감사합니다.

NodeJS Advance 1주차 스터디 정리

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.들어가며

Node를 안지 벌써 2년이 넘은것 같다. 하지만 늘 express 로 rest api 서버로만 개발만 하다 좀 더 알고 싶은 마음이 항상 있었다. 그래서 스터디 주제도 중급 노드JS로 정해서 진행하기로 했다.

NodeJS 기본 구조

  • Javascript code 작성
  • NodeJS
  • V8 또는 libuv 에서 처리

1542544892846

1542544957069

Crypto 안에 있는 pbkdf2 함수를 통해서 동작원리를 파악할 예정이다.

1542545130428

NodeJS 소스내에 pbkdf2.js 파일을 살펴보자.

소스위치

1542545471505

crypto 라는 걸 통해서 process.binding 을 해주는 걸 볼 수 있다.

c++ 로 구현된 걸 가져온 걸로 보인다. 그럼 c++ 구현 내부를 살펴보면...

node /src / node_crypto.cc

1542546067536

1542546170025

좀 더 내부 과정을 살펴보면 아래 그림과 같다고 한다.


추가적으로 c++ 와 자바스크립트 사이에 V8 를 통해서 중개해주고 있는 걸 볼 수 있다.

node /src / node_crypto.cc

1542546297557

libuv

이건 좀 더 운영체제와 관련된 내용인데 나중에 공부하도록 한다.

Thread

이벤트 루프로 들어가기전 사전지식인 Thread 에 대해서 공부하고 넘어가도록 한다.

일반적인 싱글 프로세스 형태

1542546531155

멀티 프로세스 형태

1542546560867

윈도우의 경우 작업관리자를 열고 살펴보면 Thread 갯수를 확인할수 있다.

1542546634555

스케쥴링

운영체제는 특점 시점에서 처리할 쓰레드를 결정할 수 있다.

1542546732269

하나이상의 코어가 있는 경우 여러 쓰레드를 쉽게 빠르게 처리를 할 수 있다.

1542546854687

만약에 2개의 쓰레드가 있다고 가정하고 한개의 쓰레드가 I/O 로 시간이 걸린다고 했을 경우 스케쥴러에서 따로 뺀 공간에서 처리를 하고 2번이 끝나고 1을 다시 넣어서 마무리를 할 수 있다.

1542547055938

추후 다시 한번 소스를 보면서 쓰레드 관련된 내용을 살펴볼 예정이다.

Event loop

이벤트 루프는 한 쓰레드가 무엇을 해야하는지 결정하는 제어 구조와 같다고 생각하면 된다.

1542547167583

전체적인 이벤트 루프 구조를 Pseudo code 로 살펴보자.

크게 3가지 제어 구문이 끝나야 이벤트 루프가 끝난다.

  • pendingTimers
    • Check Any pending setTimeout, setInterval, setImmediate?
  • pendingOSTasks
    • Check Any pending OS tasks? (Like server listening to port)
  • pendingOperations
    • Check Any pending long running operations? (Like fs module)
// node myFile.js

const pendingTimers = [];
const pendingOSTasks = [];
const pendingOperations = [];

// New timbers, tasks, operations are recorded from myFile running
myFile.runContents();

function shouldContinue() {
    // Check one : Any pending setTimeout, setInterval, setImmediate?
    // Check two: Any pending OS tasks? (Like server listening to port)
    // Check three: Any pending long running operations? (Like fs module)
    return pendingTimers.length || pendingOSTasks.length || pendingOperations.length;
}
// Entire body executes in one 'tick'
while(shouldContinue()) {
    // 1) Node looks at pendintTimers and sees if any functions
    // are ready to be called. setTimeout, setInterval

    // 2) Node looks at pendingOSTasks and pendingOperations
    // and calls relevant callbacks

    // 3) Pause execution. Continue when ....
    // - a new pendingOSTask is done
    // - a new pendingOperation is done
    // - a timer is about to complete

    // 4) Look at pendingTimers. Call any setImmediate

    // 5) Handle any 'close' events
}

// exit back to terminal

다음 글을 통해서 각각의 내용에 대해서 자세히 살펴볼 예정이다.

참석해주셔서 감사합니다.

apt-get update     
apt-get install python2.7    
ln -s /usr/bin/python2.7 /usr/bin/python 


윈도우 상에서 도커를 이용해서 몽고 DB 세팅

윈도우상에서 몽고디비 설치시 가끔 오류 나는 부분이 있다. 그럼 설치도 안되고 개발도 안된다.

그럴때 도커를 이용해서 빠르게 테스팅을 할수 있다.

우선 도커를 켜서 다음 명령어를 하자.

docker pull mongo
docker run --name database -d -p 27017:27017 mongo --noauth --bind_ip=0.0.0.0

27017 포트에 0.0.0.0 으로 바인딩하는 문구이다.

-noauth 는 아이디랑 비번없이 들어갈 수 있다.

만약 설정시 아래와 같이 가능하다.

docker run --name some-mongo -d mongo --auth
docker exec -it some-mongo mongo admin

db.createUser({ user: 'jsmith', pwd: 'some-initial-password', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] });

................................
Successfully added user: {
    "user" : "jsmith",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        }
    ]
}

그러면 잘 설치가 되었는지 확인해보자.

docker ps

윈도우 유저의 경우 virtucal box 에 방화벽을 열어야 한다.

virutal box 열기

기본 머신 선택한 후에 셋팅 열기

설정에 네트워크 메뉴 열기

네트워크 옵션 하단에 포워딩 메뉴 열기

그리고 줄 하나 추가 한다.

mongo / TCP / host port ( 임의로 < 1111 > ) / guest port ( 27017 )

그리고 클라이언트를 다운받는다.

보통 주로 받는 건

https://robomongo.org/

https://www.mongodb.com/download-center?jmp=hero#compass

(무료 커뮤니티 버전 선택)


그리고 마지막으로 클라이언트 접속을 해본다.

IP 는 localhost 또는 127.0.0.1 로 하고

포트는 임의로 정한 1111 로 해본다.

그럼 admin 으로 접속된걸 볼수 있다.

참고 사이트

https://codehangar.io/mongodb-image-instance-with-docker-toolbox-tutorial/

https://hub.docker.com/_/mongo/

+ Recent posts