Rev. 2.73

express.png

Node.JS용 MVC 프레임워크인 Express를 사용해 보면서, 쓰면 쓸수록 잘 만들었다는 생각이 들어 학습에 목적을 둔 날번역을 주말내내 진행했습니다. 생각보다 양이 많지 않아 약 50% 정도 진행되었으며, 주중으로 마무리 지을 예정입니다. 그런데 문서를 들여다 보면 볼수록 더욱 잘 만들어 졌었다는 생각을 지울수가 없네요. 지금까지의 놀라움을 요약해 보면, 정규식을 지원하는 강력한 라우팅 메커니즘미들웨어 개념을 도입한 말도 안되게 쉬운 기능확장, 심플한 라우트 필터링, 폼전송시 PUT 및 DELETE 메서드의 고질적인 문제를 methodOverride 미들웨어로 허무하게 해결, 응답형태가 json이면 json으로 오류를 뱉어 주시는 센스, 다중으로 템플릿 엔진을 허용하는 초간단 변수 전달 뷰 시스템까지... 아주 쩔어요. 라우트 자체에 미들웨어나 조건들를 끼워넣고 순서대로 순환해 가며 처리하는 아이디어가 Express의 핵심이라 볼 수 있겠네요. 아~ 이제 서버-사이드도 할만해 졌어... 서버-사이드로 넘어갈까봐요.

* 2011-08-23, 1차 번역 완료 했습니다. 문맥상의 오류는 차차 잡아 가겠습니다. 혹시모를 오역이나 추가의견이 있으시면 좀 알려주세요;

Comments

얼마전 LearnBoost에서 개발한 Cluster라는 Node.JS용 모듈을 알게되었습니다. 이 모듈은 노드 애플리케이션을 멀티-코어로 구동시켜 줍니다. 마스터, 워커로 간단하게 구성된 계층구조를 가지며 워커는 하나의 물리적인 코어를 가지게 되고 마스터만 죽이면 워커는 알아서 죽습니다. 또한 다양한 추가 기능을 제공하는 플러그인들이 있습니다. 특히, repl이라는 플러그인을 로드하면 telnet으로 접속할 수 있는 관리환경을 제공하게 되는데, 워커의 상황을 감시하고 즉시 죽이거나 스폰할 수있습니다. 기본적인 설치 및 사용방법은 다음과 같습니다.

# npm install cluster
# touch server.js
# vim server.js
var http = require('http');

var server = http.createServer(function(req, res){
  res.writeHead(200);
  res.end('Hello World');
});

// server start listening
if (module === require.main) server.listen(3000);
else module.exports  = server;

console.log('listen on port 3000');
# touch cluster.js
# vim cluster.js
var cluster = require('cluster');

cluster('./server')
  .use(cluster.repl('/var/run/cluster.sock'))
  .listen(3000);
# node cluster.js
listen on port 3000
listen on port 3000
listen on port 3000
listen on port 3000
listen on port 3000
listen on port 3000
listen on port 3000
listen on port 3000

# telnet /var/run/cluster.sock
cluster> help()

  Commands
  help(): Display help information
  spawn(n): Spawn one or more additional workers
  pids(): Output process ids
  kill(id, signal): Send signal or SIGTERM to the given worker
  shutdown(): Gracefully shutdown server
  stop(): Hard shutdown
  restart(): Gracefully restart all workers
  echo(msg): echo the given message
  stats(): Display server statistics

cluster> pids()
  master: 25930
  worker #0: 25933
  worker #1: 25934
  worker #2: 25935
  worker #3: 25936
  worker #4: 25937
  worker #5: 25938
  worker #6: 25939
  worker #7: 25940

제가 사용하는 가상서버의 코어는 8개입니다. 기본으로 하나의 코어에 하나의 워커를 배정하게 됩니다. 이제 포트 3000번으로 접속하는 서버 프로그램은 코어 래밸에서 클러스터링 된 것입니다. 참 쉽죠? 보다 자세한 사용방법은 api 문서를 숙지하세요.

epxpress 프레임웍으로 간단하게 만들어본 Firejune.I/O의 인덱스에 적용시키고 약 이틀정도 시간이 경과했습니다. 아주 잘돌아 가고 있어요. 주의해야 할 점은 서로다른 코어에서 작동하기 때문에 어떠한 변수를 참조할 수 없는 경우가 발생합니다. 그래서 이 변수를 워커들이 공유해야한다면 Redis와 같은 데이터스토어를 이용하여 해결해야합니다. MemoryStore를 기본으로 사용하는 Socket.IO역시 이러한 문제를 안고 있습니다. 접속이 성공적으로 이루어졌지만 상대방에게 메시지를 전송하지 못하는 상태인거죠. 그들은 사용할 Store를 옵션화하고 자체적으로 RedisStore를 제공하여 이 문제를 해결할 계획인듯 합니다.

var express= require('express')
  , sio = require('socket.io');

var app = express.createServer();
var io = sio.listen(app);
io.configure(function () {
  io.set('store', new sio.RedisStore);
});

if (module === require.main) app.listen(3000);
else module.exports  = app;

그러나 Socket.IO 최신버전(0.7.9)에서는 './lib/stores/redis.js'에서 'removeEvent'라는 속성이 없다는 오류가 발생합니다. 이 문제를 Daniel Shaw씨가 수정(그리고 이것)하여 GitHub에 풀(pull)된 상태이며 0.8 버전에서 적용될 것이라고 하네요. 이 소스를 반영하면 오류는 사라지지는데 뭔가(메모리가 누수되고 있다는;) 경고가 계속 나옵니다. 하지만 작동에는 이상이 없더군요. 송/수신 패킷이 상상을 초월하는 웹소켓에 적용하면 금상첨화 아니겠어요? 0.8이 나오기를 기다려 봐야겠습니다.

참고로, LampWebDevelopers.com에서도 자신들의 서비스에 Cluster 모듈을 적용하여 서버의 물리적 변화를 관측한 포스팅을 했습니다.

Comments

firejune.io 도메인을 구입했습니다. nic.io 도메인은 국가 도메인으로 영국령 인도양식민지의 공식 국가 도메인입니다. 국가 도메인을 구입할 수 있는 가비O에서 등록시 1년 유지비용이 22만원(VAT포함)으로 상당히 비싼편이긴 하지만 Node.JS의 모듈인 Socket.IO의 홈페이지를 보면서 네이밍 센스가 참 돋보인다는 감흥을 얻어 덜컥 구입하게 되었습니다. 말그대로 input/output을 의미하죠. 웹소켓서버에 참 어울리는 네이밍이라는 생각이 듭니다. 제가 구성한 서버는 앞단에 아파치를 올리고 각종 서브모듈들과 데몬들 설치하여 복합적으로 운영하려고 했습니다만 약 2주정도 지켜본 결과 무리가 있다는 결론을 얻었습니다. 그래서 서버단이 PHP로 만들어진 iRounte 프로젝트(iroute.mobi)는 이미 APM(Apache,PHP,MySQL) 호스팅을 받고있는 firejune.com으로 포워딩 했으며, 이번에 새로 영입한 가상서버는 Node.JS기반의 프로젝트만 적극 활용하기로 결정했습니다. 그래서 firejune.io 도메인이 필요했던거죠.

기존에는 아피치가 80포트를 점유하고 있어 아파치에서 프록시를 구현할 필요가 있었는데, 이게 상당히 비효율 적이었습니다. 이제는 Node.JS에서 node-http-proxy 모듈을 이용하여 80포트를 점유하도록 하고 프록시 서버를 다음과 같이 구성하였습니다. 서버단 코드가 자바스크립트라서 그런지 몰라도 생각했던대로 순순히 만들어져 주더군요.

node-http-proxy 설치:

# npm install http-proxy

프록시 서버 구동 파일 작성:

var http = require('http')
  , httpProxy = require('http-proxy');

var routeMaps = {
    //  'hummingbird.firejune.io': '127.0.0.1:82'
    , 'chat.firejune.io': '127.0.0.1:88'
    , 'wpilot.firejune.io': '127.0.0.1:89'
    , 'scrumblr.firejune.io': '127.0.0.1:83'
    //, 'smails.firejune.io': '127.0.0.1:100'
};

var proxy = new httpProxy.HttpProxy({router: routeMaps});
var server = http.createServer(function(req, res) {
  if (routeMaps[req.headers.host])
    return proxy.proxyRequest(req, res);

  if (req.headers.host.substr(0, 3) == 'www') {
    res.writeHead(301, {
        'Location': 'http://firejune.io'
      , 'Expires': (new Date).toGMTString()
    });
    return res.end();
  }

  ...

});

// server start listening
if (module === require.main) server.listen(80);
else module.exports  = server; 

프록시 서버를 구성한 이유는 한 서버에서 여러 서브 프로젝트를 돌리기 때문입니다. 해당 프로젝트로의 접근을 용이하게 하기 위한 구성이라는 것을 알수 있습니다. 그래서 프록시 테이블을 이용한 것이죠. 장점이라면 이 프로세스만 관리하면 유지/보수가 간단하다라는 것입니다. 그러나 단점은 비효율적이라는 측면입니다. 프로세스가 두개 도느냐 하나도느냐의 차이니까 당연한 거겠죠. 그래서 빈번한 통신이 발생하는 웹소켓은 listen한 곳을 그대로 사용하는 것이 좋습니다. views 없이 소켓만을 제공하는 프로젝트는 어차피 사용자가 주소를 직접 입력하고 접근할일은 없을테니까 말예요. 효율적으로 프록시를 구성하고 싶다면 http-proxy 모듈을 프로젝트마다 로드하여 라우팅하는 것이 좋습니다. 하지만 관리가 힘들겠죠?

Comments