리셋 되지 말자

[nodejs 교과서] 4장 본문

NodeJS

[nodejs 교과서] 4장

kyeongjun-dev 2020. 10. 28. 15:06

4.5 cluster

cluster 모듈은 기본적으로 싱글 프로세스로 동작하는 node가 CPU 코어를 모두 사용할 수 있게 해주는 모듈이다. 포트를 공유하는 노드 프로세스를 여러 개 둘 수도 있으므로, 요청이 많이 들어왔을 때 병렬로 실행된 서버의 개수만큼 요청을 분산되게 할 수 있다. 즉 서버에 무리가 덜 가게 되는 셈이라고 한다.

예를 들어 코어가 8개인 개인 서버가 있을 때, node는 보통 코어를 하나만 활용한다. 하지만 cluster 모듈을 설정하여 코어 하나당 노드 프로세스 하나가 돌아가게 할 수 있다. 성능이 꼭 여덟 배가 되는 것은 아니지만 코어를 하나만 사용할 때에 비해 성능이 개선됩니다. 하지만 장점만 있는 것은 아니며, 메모리를 공유하지 못하는 등의 단점도 있다. session을 메모리에 저장하는 경우 문제가 될 수 있다. 이는 레디스 등의 서버를 도입하여 해결할 수 있다.

아래는 server1.js의 클러스터링을 한 코드이다.

  • cluster.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if(cluster.isMaster){
    console.log('마스터 프로세스 아이디: ', `${process.pid}`);
    // CPU 개수만큼 워커를 생산
    for(let i=0; i<numCPUs; i++){
        cluster.fork();
    }

    //워커가 종료되었을 때
    cluster.on('exit', (worker, code, signal)=>{
        console.log(`${worker.process.pid}번 워커가 종료되었습니다`);
        console.log('code', code, 'signal', signal);
    });

}else{
    // 워커들이 포트에서 대기
    http.createServer((req, res)=>{
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        res.write('<h1>Hello Node</h1>');
        res.end('<p>Hello Cluster</p>');
    }).listen(8000);

    console.log(`${process.pid}번 워커 실행`);
}

// 결과
$ node cluster.js
마스터 프로세스 아이디:  11476
12532번 워커 실행
17260번 워커 실행
8256번 워커 실행
19756번 워커 실행
21064번 워커 실행
15792번 워커 실행
11868번 워커 실행
17688번 워커 실행
5836번 워커 실행
19532번 워커 실행
24024번 워커 실행
10460번 워커 실행
18256번 워커 실행
1196번 워커 실행
13864번 워커 실행
16856번 워커 실행

worker_threads의 예제와 모양이 비슷하다. 16코어 컴퓨터라서 16개의 워커 프로세스가 생성된 모습이다. 다만 스레드가 아니라 프로세스 이다. 클러스터에는 마스터 프로세스와 워커 프로세스가 있다. 마스터 프로세스는 CPU 개수만큼 워커 프로세스를 만들고, 8000번 포트에서 대기한다. 요청이 들어오면 만들어진 워커 프로세스에 요청을 분배한다.

 

클러스터링

워커 프로세스가 실질적인 일을 하는 프로세스 이다. 실제로 코어 수만큼 생성되었는지 확인을 위해 아래와 같이 코드를 수정한다.

  • cluster.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
console.log(numCPUs);
if(cluster.isMaster){
    console.log('마스터 프로세스 아이디: ', `${process.pid}`);
    // CPU 개수만큼 워커를 생산
    for(let i=0; i<numCPUs; i++){
        cluster.fork();
    }

    //워커가 종료되었을 때
    cluster.on('exit', (worker, code, signal)=>{
        console.log(`${worker.process.pid}번 워커가 종료되었습니다`);
        console.log('code', code, 'signal', signal);
    });

}else{
    // 워커들이 포트에서 대기
    http.createServer((req, res)=>{
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        res.write('<h1>Hello Node</h1>');
        res.end('<p>Hello Cluster</p>');
        setTimeout(()=>{ // 워커가 존재하는지 확인하기 위해 1초마다 강제 종료
            process.exit();
        }, 1000);
    }).listen(8000);

    console.log(`${process.pid}번 워커 실행`);
}

// 결과
$ node cluster.js
마스터 프로세스 아이디:  24528
24248번 워커 실행
11756번 워커 실행
17556번 워커 실행
18616번 워커 실행
3652번 워커 실행
19132번 워커 실행
22900번 워커 실행
20016번 워커 실행
6680번 워커 실행
16224번 워커 실행
15320번 워커 실행
10436번 워커 실행
24016번 워커 실행
9776번 워커 실행
24392번 워커 실행
8168번 워커 실행
// 여기서부터 서버 접속
8168번 워커가 종료되었습니다
code 0 signal null
24392번 워커가 종료되었습니다
code 0 signal null
9776번 워커가 종료되었습니다
code 0 signal null
24016번 워커가 종료되었습니다
code 0 signal null
10436번 워커가 종료되었습니다
code 0 signal null
15320번 워커가 종료되었습니다
code 0 signal null
16224번 워커가 종료되었습니다
code 0 signal null
6680번 워커가 종료되었습니다
code 0 signal null
20016번 워커가 종료되었습니다
code 0 signal null
22900번 워커가 종료되었습니다
code 0 signal null
19132번 워커가 종료되었습니다
code 0 signal null
3652번 워커가 종료되었습니다
code 0 signal null
18616번 워커가 종료되었습니다
code 0 signal null
17556번 워커가 종료되었습니다
code 0 signal null
11756번 워커가 종료되었습니다
code 0 signal null
24248번 워커가 종료되었습니다
code 0 signal null

요청이 들어올 때마다 1초 후에 서버가 종료되도록한 코드다. 접속해보면 1초 후에 하나씩 process가 종료되는 것을 확인할 수 있다. 모든 워커가 종료되면 서버가 응답하지 않는다.

코드(code)는 process.exit()의 인수로 넣어준 코드가 출력되고, 신호(signal)는 존재하는 경우 프로세스를 종료한 신호의 이름이 출력된다.

워커 프로세스가 존재하므로 코어 수만큼 까지는 오류가 발생해도 서버가 정상 작동할 수 있다는 뜻이다. 종료된 워커를 다시 켜면 오류가 발생해도 계속 버틸 수 있다. 아래와 같이 워커 프로세스가 종료되었을 때 새로 하나를 생성해본다.

 

  • cluster.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if(cluster.isMaster){
    console.log('마스터 프로세스 아이디: ', `${process.pid}`);
    // CPU 개수만큼 워커를 생산
    for(let i=0; i<numCPUs; i++){
        cluster.fork();
    }

    //워커가 종료되었을 때
    cluster.on('exit', (worker, code, signal)=>{
        console.log(`${worker.process.pid}번 워커가 종료되었습니다`);
        console.log('code', code, 'signal', signal);
        cluster.fork();
    });

}else{
    // 워커들이 포트에서 대기
    http.createServer((req, res)=>{
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        res.write('<h1>Hello Node</h1>');
        res.end('<p>Hello Cluster</p>');
        setTimeout(()=>{ // 워커가 존재하는지 확인하기 위해 1초마다 강제 종료
            process.exit();
        }, 1000);
    }).listen(8000);

    console.log(`${process.pid}번 워커 실행`);
}

// 결과
$ node cluster.js
마스터 프로세스 아이디:  23536
5268번 워커 실행
11044번 워커 실행
15264번 워커 실행
17924번 워커 실행
3368번 워커 실행
10084번 워커 실행
1032번 워커 실행
12504번 워커 실행
23892번 워커 실행
16760번 워커 실행
7948번 워커 실행
9776번 워커 실행
32번 워커 실행
20604번 워커 실행
16560번 워커 실행
16136번 워커 실행
16136번 워커가 종료되었습니다
code 0 signal null
19132번 워커 실행
19132번 워커가 종료되었습니다
code 0 signal null
11308번 워커 실행

접속한 뒤 1초 후에 워커가 종료되고, 종료되면 새로운 워커가 실행되는 것을 확인할 수 있다. 하지만 이런 방식으로 오류를 처리하는 것은 좋지 않은 생각이다. 오류 자체의 원인을 찾아서 해결하여야 한다. 그래도 예기치 못한 에러로 인해 서버가 종료되는 현상을 방지할 수 있어 클러스터링을 적용해두는 것이 좋다.

직접 cluster 모듈로 클러스터링을 구현할 수도 있지만, 실무에서는 pm2 등의 모듈로 cluster 기능을 사용하곤 한다. pm2 모듈은 15.1.5절에서 설명한다.


4.6 함께 보면 좋은 자료

  • http 모듈 소개

nodejs.org/dist/latest-v14.x/docs/api/http.html

  • 쿠키 설명

developer.mozilla.org/ko/docs/Web/HTTP/Cookies

  • 세션 설명

developer.mozilla.org/ko/docs/Web/HTTP/Session

  • https 모듈 소개

nodejs.org/dist/latest-v14.x/docs/api/https.html

  • http2 모듈 소개

nodejs.org/dist/latest-v14.x/docs/api/http2.html

  • cluster 모듈 소개

nodejs.org/dist/latest-v14.x/docs/api/cluster.html

Comments