본문 바로가기

Programming/Node.js

5. http 모듈로 서버 만들기 (5) - cluster [2/2]

node js logo image

 

 

 

 

 

앞서 작성했던 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 += 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}번 워커 실행`);
}

 

 

여기 클러스터에서는 마스터 프로세스 / 워커 프로세스가 존재합니다. 마스터 프로세스의 역할은, CPU 개수만큼 워커 프로세스를 만드는 것이죠. 그리고 예제대로면 8086번 포트에서 대기하게 됩니다. 이 때, req가 들어오게 되면 만들어진 워커 프로세스에 요청을 분배하게 됩니다. 여기서 워커 프로세스가 실질적으로 일을 하게 되는 프로세스입니다. 

 

 

 

이제 CPU 코어에 맞춰서 워커가 생성되는지 여부를 확인해 보도록 하겠습니다. 아래 예제는 위 코드를 살짝 수정했습니다. req가 들어올 때마다 1초 후에 서버가 하나씩 종료되도록 했습니다. 

 

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 += 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 Clduster</p>');
        setTimeout(() => {
            process.exit(1);
        }, 1000);
    }).listen(8086);

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

 

 

node cluster.js를 실행한 다음, http://localhost:8086에 접속하고 1초 후 콘솔에 워커 종료 메시지가 출력됩니다. 이제 계속 새로고침을 해서 req를 보내게 되면 모든 워커가 종료되고 서버가 응답하지 않게 됩니다. 

 

/* 출력
마스터 프로세스 아이디: 4348
8664번 워커 실행
25972번 워커 실행
14304번 워커 실행
16904번 워커 실행
22288번 워커 실행
26960번 워커 실행
12160번 워커 실행
7612번 워커 실행
3348번 워커 실행
6032번 워커 실행
23608번 워커 실행
716번 워커 실행
3988번 워커 실행
27644번 워커 실행
13328번 워커 실행
21612번 워커 실행


21612 번 워커가 종료되었습니다.
code 1 signal null
13328 번 워커가 종료되었습니다.
code 1 signal null
27644 번 워커가 종료되었습니다.
code 1 signal null
3988 번 워커가 종료되었습니다.
code 1 signal null
716 번 워커가 종료되었습니다.
code 1 signal null
23608 번 워커가 종료되었습니다.
code 1 signal null
6032 번 워커가 종료되었습니다.
code 1 signal null
7612 번 워커가 종료되었습니다.
code 1 signal null
3348 번 워커가 종료되었습니다.
code 1 signal null
12160 번 워커가 종료되었습니다.
code 1 signal null
26960 번 워커가 종료되었습니다.
code 1 signal null
22288 번 워커가 종료되었습니다.
code 1 signal null
16904 번 워커가 종료되었습니다.
code 1 signal null
14304 번 워커가 종료되었습니다.
code 1 signal null
25972 번 워커가 종료되었습니다.
code 1 signal null
8664 번 워커가 종료되었습니다.
code 1 signal null
*/

 

위에서 process.exit의 인수로 전달된 코드가 출력되는 동시에 signal이 존재하는 경우 프로세스를 종료시킨 신호 이름도 출력됩니다. 

 

이론적으로, 위와 같은 방식으로 구현하게 되면 워커 프로세스가 존재하는 만큼 오류가 발생해도 서버가 작동이 가능한 상태라고 볼 수 있습니다. 여기에 코드를 조금 보강해 종료된 워커를 다시 켤 수도 있습니다. 물론 이는 이론적인 개념이므로 실제로는 오류 자체를 근본적으로 해결해야 합니다. 

 

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

 

 

위 코드를 실행한 후, 동일하게 req를 반복해서 보낼 경우 아래와 같이 새로운 워커가 실행되는 것을 볼 수 있습니다. 

 

/*
마스터 프로세스 아이디: 11188
16364번 워커 실행
14436번 워커 실행
14432번 워커 실행
10156번 워커 실행
6816번 워커 실행
22872번 워커 실행
10768번 워커 실행
16776번 워커 실행
18984번 워커 실행
1748번 워커 실행
23832번 워커 실행
24036번 워커 실행
24540번 워커 실행
15108번 워커 실행
24636번 워커 실행
10216번 워커 실행
10216 번 워커가 종료되었습니다.
code 1 signal null
28640번 워커 실행
28640 번 워커가 종료되었습니다.
code 1 signal null
13212번 워커 실행
24636 번 워커가 종료되었습니다.
code 1 signal null
18908번 워커 실행
13212 번 워커가 종료되었습니다.
code 1 signal null
11504번 워커 실행
*/

 

 

실제로는 직접 클러스터링을 구현하기 보다는 pm2 등의 모듈을 사용하는데, 이는 따로 살펴보도록 하겠습니다. 

 

 

 

참고로 라우팅을 위해서는 HTML / CSS 파일 요청 주소와 서버의 자원을 요청하는 주소로 나눠지게 됩니다(예제의 users 자원 요청). 이 때 if문이 많아지게 되면 코드 가독성에 문제가 생기고, 쿠키와 세션이 추가되면 코드는 기하급수적으로 늘어날 것입니다. 이를 위해서 Express 모듈을 사용해 간단하게 처리하게 됩니다. 이는 다음 아티클부터 살펴보도록 하겠습니다.