使用 node.js + nginx 建设网站

昨天搞定了一个小网站的搭建,用了 node.js,另外为了能在一个 VPS 上搭建多个网站,用了 nginx 作为反向代理。

软件介绍

嗯,从维基上复制了一下~

node.js

Node.js是一个事件驱动I/O伺服端JavaScript环境,基于V8。目的是为了提供撰写可扩充网络程式,如web服务。第一个版本由Ryan Dahl于2009年释出,后来,Joyent雇用了Dahl,并协助发展Node.js。

nginx

nginx(发音同engine x)是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代理服务器以及电子邮件(IMAP/POP3)代理服务器。

cluster

在 node.js 0.6.0 之前,有一个第三方的 node.js 模块 cluster,用来进行多核服务器上运行 node.js,以及提供扩展的支持。但是在 node.js 0.6.0 之后,node.js 本身就提供了 cluster 的支持,另外,第三方的 cluster 也与 node.js 0.6 有兼容性问题。目前 node.js 的稳定版本是 0.6.5,因此需要使用原生的 cluster 来代替第三方的 cluster。

幸好内置的 cluster 也足够简单,如果只是为了多核负载均衡,以及支持即时服务重启的话,只需要写一点的代码就可以完成这些功能了。

server.js

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

var NODE_ENV = process.env.NODE_ENV || 'production';
var appName = path.basename(__dirname);
var appPort = 9000;

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

if (cluster.isMaster) {
    process.title = appName + ' master';
    console.log(process.title, 'started');

    // 根据 CPU 个数来启动相应数量的 worker
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    process.on('SIGHUP', function() {
        // master 进程忽略 SIGHUP 信号
    });

    cluster.on('death', function(worker) {
        console.log(appName, 'worker', '#' + worker.pid, 'died');
        cluster.fork();
    });

} else {
    process.title = appName + ' worker ' + process.env.NODE_WORKER_ID;
    console.log(process.title, '#' + process.pid, 'started');

    process.on('SIGHUP', function() {
        // 接收到 SIGHUP 信号时,关闭 worker
        process.exit(0);
    });

    http.Server(function(req, res) {
        res.writeHead(200);
        res.end('Worker ' + process.env.NODE_WORKER_ID);
    }).listen(8000);
}

运行服务 node server.js

nodejs master started
nodejs worker 1 #38928 started
nodejs worker 3 #38930 started
nodejs worker 2 #38929 started
nodejs worker 4 #38931 started

如果直接 kill 掉某一个 worker,kill 38928

nodejs worker #38931 died
nodejs worker 5 #38934 started

可以看到一个新的 worker 会马上启动,这就保证了服务的不间断性。

Virtual Host 支持

通常情况下,我们不会在一个 IP 上只部署一个网站。在使用 node.js 时,可以使用 connect 提供的 vhost 支持 Virtual Host,但是,这也限制了服务器只能用 node.js,而不能同时使用其他的服务,例如再安装一个 PHP 服务之类。

这时就可以使用 nginx 的反向代理来解决了,用户在访问网站时,请求先到 nginx 进行处理,如果是 node.js 站点的话,将请求转发到 node.js 的服务,然后再将 node.js 服务的结果返回给用户。

在 nginx 中设置反向代理很简单,一句 proxy_pass 就可以搞定:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://localhost:9000;
    }
}

在添加了 Virtual Host 之后,就可以把一些静态资源,例如 CSS、JavaScript 之类的文件,直接交给 nginx 来处理,而不是什么请求都需要到 node.js 这一层去处理,也省去反向代理这一关的消耗。

Session 支持

使用 express 这个 node.js web framework 来创建网站时,可以配合 connect 这个中间件来实现 session 支持。

默认情况下,connect 的 session 是使用内置的内存存储来存放 session 信息,这时如果 node.js 服务一旦重启,所有的 session 信息都会丢失,这对于用户来说不是个好体验,那么我们可以用外部的存储来存放 session 信息,例如 redis。

要让 connect 使用 redis 作为 session 存储的话也是很方便的:

var express = require('express');
var RedisStore = require('connect-redis')(express);

var app = express.createServer(
    express.session({ secret: 'keyboard cat', store : new RedisStore() })
);

监测文件改动

在调试的时候,经常需要重新启动 node.js 以便修改过的文件生效,原来第三方的 cluster 有一个配置项,可以很方便的配置监测时间间隔,文件改动后自动重新启动 worker,但是原生的 cluster 就没有这个功能了,需要自己来实现。

fs 模块提供了 watch 函数,可以方便的监测文件修改,使用这个就可以来实现文件修改后自动重启 woker 功能了。

if (cluster.isMaster) {
    process.title = appName + ' master';
    console.log(process.title, 'started');

    var workers = [];

    // 根据 CPU 个数来启动相应数量的 worker
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        workers.push(worker.pid);
    }

    process.on('SIGHUP', function() {
        // master 进程忽略 SIGHUP 信号
    });

    // 监测文件改动,如果有修改,就将所有的 worker kill 掉
    fs.watch(__dirname, function(event, filename) {
        workers.forEach(function(pid) {
            process.kill(pid);
        });
    });

    cluster.on('death', function(worker) {
        var index = workers.indexOf(worker.pid);
        if (index != -1) {
            workers.splice(index, 1);
        }
        console.log(appName, 'worker', '#' + worker.pid, 'died');
        worker = cluster.fork();
        workers.push(worker.pid);
    });

}

这样,每次文件保存之后,node.js 都会自动重启,从而避免了每次保存文件要手动重启服务的麻烦。

当然,在使用监测文件自动重启的时候,最好加上 NODE_ENV 的判断,在 development 的时候才进行自动重启,而 production 的时候使用手动重启就够了。

小结

总的来说,使用 node.js 来构建网站还是很方便的,加上 nginx 反向代理之后,与使用 PHP 之前也没有很大的区别,又可以享受到 node.js 的高效。

嗯,就这样了,希望此文对你有所帮助。

参考资料

  1. https://zh.wikipedia.org/wiki/Node.js
  2. http://nodejs.org/docs/v0.6.5/api/cluster.html
  3. http://learnboost.github.com/cluster/
  4. http://expressjs.com/
  5. http://senchalabs.github.com/connect/
  6. http://redis.io/

--- EOF ---

发表评论?

6 条评论。

  1. 不错的。改天我也玩玩。
    (PS: 一个这么好的博客,现在才发现 :evil: )

  2. 我也觉得楼主博客很棒,不过今天搜索Canvas才发现
    可惜了。不知可否交换一个博客链接 正在学习JS和PHP

  3. 请问一下:nginx做反向代理的时候,nodejs使用express3,用log3js记录日志,所有remote_ip都是127.0.0.1。

    [2013-06-20 03:18:04.978] [WARN] [default] – 127.0.0.1 – – “GET /js/jquery-1.9.1.min.js HTTP/1.bKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36”
    GET /css/my.css 304 1ms

  4. node.js fock 新线程的时候,能不能确保在空闲的 CPU 里运行新线程

  5. node.js + nginx 入門設定 | Fntsr's Blog - pingback on 4月 27, 2014 在 1:22 上午

回复给 ixiaozhi ¬
取消回复


注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Trackbacks and Pingbacks: