node.js의 http와 cluster를 이용하여 멀티 프로세스 서버를 구성한다고 했을 때,

cluster의 worker가 죽으면 master가 자동으로 worker를 다시 fork하는 방식으로 불사신으로 돌릴 수 있다.

 

근데 worker가 불의의 사고로 죽든, 서버 재시작을 위해 죽든, 

죽는 당시에 처리하고 있던 클라이언트는 어떻게 되는지 궁금했다.

처리하고 있던 클라이언트는 마저 처리하고 죽어야 graceful이니까.


핵심은 http.server.close() 였다.

이게 호출된 순간, 

해당 http.server는 더이상 클라이언트를 받지 않는 상태가 되고, 처리하던 클라이언트는 마저 처리한 후 닫힌다.


간단한 에코형식의 불사신 http 서버를 하나 짜서 테스트를 해봤다.


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

// MASTER
if (cluster.isMaster) {
    // worker는 2개만 띄운다
    cluster.fork();
    cluster.fork();

    // worker가 죽으면 하나 더 띄움
    cluster.on('exit', function(worker, code, signal){
        console.log('worker '+worker.process.pid+' 죽었음');
        cluster.fork();
    });
}
// WORKER
else {
    var server = http.createServer(function(req, res){
        // kill이 들어온 후에도 연결된 클라이언트를 시뮬레이션하기 위해 지연실행
        setTimeout(function(){
            res.writeHead(200, {'Content-Type': 'text/plain'});
            // worker의 pid 반환
            res.end(process.pid+"\n");
        }, 5000);
    }).listen(9090, function(err){
        console.log('LISTENING : '+process.pid);
    });

    // kill 시그널
    process.on('SIGTERM', function(){
        // graceful restart
        server.close(function(){
            process.exit(0);
        });
    });
}



이 소스코드로 node를 실행시키면 worker 2개가 fork 되고 LISTEN하고 있는 worker의 pid가 아래처럼 나온다.

[root@localhost]# node ./server.js

LISTENING : 29569

LISTENING : 29570



shell을 하나 더 열어서 테스트를 해봄.

[root@localhost]# curl http://localhost:9090

// 5초후

29569

[root@localhost]# curl http://localhost:9090

// 5초후

29570

// cluster가 round-robin으로 돌리므로 다음 요청은 29569가 처리할 것이다.

// 미리 kill 29569를 준비해 둔 후 curl을 백그라운드로 실행시키고 곧바로 kill을 하고 기다려보면 반환이 일어나고 나서 worker가 죽는걸 확인할 수 있다.

[root@localhost]# curl http://localhost:9090 &

[1] 29581

// 곧바로 kill

[root@localhost]# kill 29569

// 5초후 반환값이 출력됨

[root@localhost]# 29569

[1]+  Done                    curl http://localhost:9090


// 이 시점에서 실행한 서버쪽 shell을 보면 "worker 29569 죽었음" 이 출력되어 있고 새로 worker 하나가 fork 되었음을 확인할 수 있음

// 아래는 서버쪽 출력상황

[root@localhost]# node ./server.js

LISTENING : 29569

LISTENING : 29570

worker 29569 죽었음

LISTENING : 29686



일단 처리중이던 클라이언트를 처리하고 죽는다는 건 확인했고,

kill 시그널이 들어간 다음에 요청된 클라이언트는 다른 worker에서 처리하는지 테스트 했다.

// 위와 동일한 방법으로 round-robin에서 다음 요청을 처리할 worker의 pid가 29570이라고 가정함.

// 일단 백그라운드 curl 요청 (29570 worker가 받겠지)

[root@localhost]# curl http://localhost:9090 &

[1] 29796

// 곧바로 29570 죽임

[root@localhost]# kill 29570

// 그리고 곧바로 동일한 백그라운드 curl 요청을 3회 실행 (이건 29686 worker가 받아야 함)

[root@localhost]# curl http://localhost:9090 &

[2] 29799

[root@localhost]# curl http://localhost:9090 &

[3] 29801

[root@localhost]# curl http://localhost:9090 &

[4] 29803

// 5초후 반환값의 순서는 29570이 1번, 29686이 3번 

[root@localhost]# 29570

29686

29686

29686


[1]   Done                    curl http://localhost:9090

[2]   Done                    curl http://localhost:9090

[3]-  Done                    curl http://localhost:9090

[4]+  Done                    curl http://localhost:9090


// 29570 worker는 최초의 요청을 처리하고 죽고

// kill은 들어갔지만 아직 29570 worker가 살아있는 시점에서 server.close가 호출되었으므로,

// 그 이후로 요청된 클라이언트는 전부 29686 worker에서 처리함.

// 아래는 서버쪽 출력상황

[root@localhost]# node ./server.js

LISTENING : 29569

LISTENING : 29570

worker 29569 죽었음

LISTENING : 29686

worker 29570 죽었음

LISTENING : 29805



이것만 알고 있으면 http+cluster로 만든 서버에서 graceful restart는 얼마든지 취향대로 구현할 수 있을 듯.


[참고]

http://joseoncode.com/2014/07/21/graceful-shutdown-in-node-dot-js











Posted by bloodguy
,