본문 바로가기

Node.js

Node.js - 클러스터 (Cluster)

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

 

예를 들어 코어가 8개인 서버가 있을 경우 노드는 기본적으로 코어를 하나만 활용한다. 그러나 cluster 모듈을 설정하여 코어 하나당 노드 프로세스 하나가 돌아가게 할 수 있다. 성능이 꼭 8배가 되진 않으나 하나만 사용할 때에 비해선 당연히 성능이 개선된다. 그러나 메모리를 공유하지 못한다는 단점이 있다. 이 경우 레디스 등의 서버를 도입하여 해결할 수 있다.

 

아래와 같은 server1.js 파일을 만들어 클러스터링해보자.

 

const cluster = require('cluster');
const http = require('http');

const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log(`마스터 프로세스 아이디: ${process.pid}`);
    for (let i = 0; i < numCPUs; i += 1) {
        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(8086);

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

 

결과

 

PS C:\Users\bumsu\nodejs-projects\노드js교과서\4\4.5> node server1
마스터 프로세스 아이디: 4132
19948번 워커 실행
12548번 워커 실행
10268번 워커 실행
14044번 워커 실행
21740번 워커 실행
16700번 워커 실행
7472번 워커 실행
9832번 워커 실행

 

클러스터에는 마스터 프로세스와 워커 프로세스가 있다. 마스터 프로세스는 CPU 개수만큼 워커 프로세스를 만들고, 8086번 포트에서 대기한다. 요청이 들어오면 만들어진 워커 프로세스에 요청을 분배한다. 내 컴퓨터는 옥타코어 CPU이기 때문에 워커가 여덟 개 생성되었다. 실제 동작하는지를 확인하기 위해 요청이 들어올 때마다 1초 후 서버가 종료되도록 해보자.

 

위 코드의 else 부분에 setTimeout 함수를 아래와 같이 넣어주자.

 

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(1);
        }, 1000);
    }).listen(8086);

 

결과

 

PS C:\Users\bumsu\nodejs-projects\노드js교과서\4\4.5> node server1
마스터 프로세스 아이디: 7120
17808번 워커 실행
1860번 워커 실행
21040번 워커 실행
2556번 워커 실행
22452번 워커 실행
20424번 워커 실행
3112번 워커 실행
20988번 워커 실행
20988번 워커가 종료되었습니다.
code 1 signal null
3112번 워커가 종료되었습니다.
code 1 signal null
20424번 워커가 종료되었습니다.
code 1 signal null
22452번 워커가 종료되었습니다.
code 1 signal null
2556번 워커가 종료되었습니다.
code 1 signal null
21040번 워커가 종료되었습니다.
code 1 signal null
1860번 워커가 종료되었습니다.
code 1 signal null
17808번 워커가 종료되었습니다.
code 1 signal null

 

8086포트에 한 번 접속할 때 마다 1초 후 콘솔에 워커가 하나씩 종료되었다는 메시지가 뜬다. 워커 프로세스가 존재하기에 여섯 번까지는 오류가 발생해도 서버가 정상 작동할 수 있다는 뜻이다. 종료된 워커를 다시 켜면 오류가 발생해도 계속 버틸 수 있다. 다음과 같이 워커 프로세스가 종료될 시 새로 하나를 만드는 작업을 해보자. cluster.on('exit', ...) 안에 아래와 같은 코드를 넣어준다.

 

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

 

실행 후 결과

 

PS C:\Users\bumsu\nodejs-projects\노드js교과서\4\4.5> node server1
마스터 프로세스 아이디: 19368
23140번 워커 실행
15020번 워커 실행
8048번 워커 실행
1716번 워커 실행
16804번 워커 실행
10620번 워커 실행
21384번 워커 실행
19844번 워커 실행
19844번 워커가 종료되었습니다.
code 1 signal null
23200번 워커 실행
23200번 워커가 종료되었습니다.
code 1 signal null
9040번 워커 실행
9040번 워커가 종료되었습니다.
code 1 signal null
23544번 워커 실행

 

워커가 종료될 때 마다 새로운 워커 하나가 생성된다. 그러나 이러한 방식으로 오류를 처리하는 것은 올바르지 못하다. 오류 자체의 원인을 찾아 해결하는 것이 더 좋다. 그러나 예기치 못한 에러로 인해 서버가 종료되는 현상을 방지할 수 있기 때문에 클러스터링은 적용해두는 것이 좋다.

 

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

 

출처

Node.js 교과서 개정 2판 - 길벗, 조현영