MAC中nodejs部署(参考自 nodejs开发指南)

在开发的过程中,通过node app.js 􏰤􏱎运行服务器􏰓可。但它不适合在产􏱏环境下使用,因为到目前为􏱒这个服务 器还有几个重大􏱓􏱔问题

##存在问题

###不支持故障恢复
不知你是􏱜在调试的过程中注意,􏱝程序有错误发生时,􏱞整个进程就会结束,需要重新在终端中􏱠动服务器。这一点在开发中无可厚非,但在产􏱏环境下就是􏱡重的问题 了,因为一􏱢用􏰑访问时触发了程序中􏱣个􏱤􏱥的bug􏱦􏱞个服务器就􏱧􏱨了,所以我们在部署nodejs的时候一定要注意故障恢复

###没有日志
没有错误日志和访问日志

###无法利用多核
Nodejs是单线程的,一个进程只能使用一个CPU核心

###独占端口
假如􏱞个服务器只有一个网站,或者可以给每个网站分配一个􏰇􏰈的IP地址,不会有 端口􏰦􏰧冲突的问题。而很多时􏰯为了􏲉充分利用服务器的资源,我们会在同一个服务器上建􏰈多个网站,而且这些网站可能有的是PHP,有的是Rails,有的是Node.js。不能每个进程都􏰇􏲊80端口,所以我们有􏱶要通过􏲋􏲌代理来实现基于域􏰥名的端口共享。

###需要手动启动
先前每次􏱠动服务器都是通过在􏰤􏱎行中􏰡接􏲑入􏰤􏱎来实现的,但在产􏱏环境中, 特别是在服务器重􏱠启以后,全部􏱭􏲒手动是不现实的。因此,我们应该制作一个自动启􏱠动服务器的脚􏲓本,并且通过该􏲓脚本可以实现􏲔􏱒服务器等功能。

##解决问题

###日志功能
Express有两种模式:调试模式、产品模式,产品模式易于部署,设置模式方法:在MAC命令行

export NODE_ENV=production

我们实现访问􏱲􏱳日志和错误􏱲􏱳功能。访问日志􏱲􏱳就是􏲚录用􏰑对服务器的每个请求,包括􏲛􏰑端IP地址,访问时间,访问路径,服务器􏲜应以及􏲛􏰑端代理字符􏲝。而错误日志则􏲚录程序发生错误时的􏲞􏲟,由于调试中需要􏰓时􏰼看错误􏲞􏲟,将所有错误􏰡接显 示到终端􏰓可,而在产􏱏模式中,需要写入错误􏱲􏱳文件。
Express 提供了一个访问􏱲􏱳中间件,只需指定stream 参数为一个输出流􏰓可将访问日志写入文件。打开app.js,在最上方加入以下代码:

var fs = require('fs');
var accessLogfile = fs.createWriteStream('access.log',{flags:'a'});
var errorLogfile = fs.createWriteStream('error.log',{flags:'a'});

􏲠􏲡􏲢然后在app.configure第一行加入

app.use(express.logger({stream:accessLogfile}));

至于错误􏱲􏱳,需要􏰒􏰇实现错误􏲜,修􏲪改如下:

app.configure('production', function(){
  app.use(function(err,req,res,next){
    var meta = '[' + new Date() +']' +req.url + '\n';
    errorLogfile.write(meta + err.stack + '\n');
    next();
  });
//  app.use(express.errorHandler());
});

为了产生一个错误,我们􏲪改routes/index.js 中􏰽 / 􏰾的􏲜应函数,加入以下代码:

throw new Error('An error for test purposes.');

完整的app.js代码如下

/**
 - Module dependencies.
 */
var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , path = require('path')
  , markdown=require('markdown-js')
  , fs = require('fs');

//写入日志
var accessLogfile = fs.createWriteStream('access.log',{flags:'a'});
var errorLogfile = fs.createWriteStream('error.log',{flags:'a'});

var app = express();
var MongoStore = require('connect-mongo')(express);

var settings = require('./settings');
// Configuration

app.configure(function(){
  app.set('port', process.env.PORT || 3300);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.favicon(__dirname+'/public/logo.ico'));
//  app.use(express.logger('dev'));
  app.use(express.logger({stream:accessLogfile}));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser());
  app.use(express.session({
    secret: settings.cookieSecret,
    store: new MongoStore({
      db: settings.db
    })
  }));
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(function(err,req,res,next){
    var meta = '[' + new Date() +']' +req.url + '\n';
    errorLogfile.write(meta + err.stack + '\n');
    next();
  });
//  app.use(express.errorHandler());
});

app.engine('md', function(path, options, fn){  
  fs.readFile(path, 'utf8', function(err, str){  
    if (err) return fn(err);  
    str = markdown.parse(str).toString();  
    fn(null, str);  
  });
});

// Routes

app.get('/', routes.index);
app.get('/home', routes.home);
app.get('/about', routes.about);
app.get('/report', routes.report);
app.get('/submit', routes.getsubmit);
app.post('/submit', routes.postsubmit);
app.get('/login', routes.getlogin);
app.post('/login', routes.postlogin);

app.get('/logout', routes.logout);

app.post('/modify', routes.modify);
app.get('/login', routes.getlogin);

app.get('/doc/:author/:title', routes.getdoc);

app.get('/doc', routes.getdocindex);

app.get('/xxx', function(req, res) {  
    console.log('404 handler..')  
    res.render('404', {  
        status: 404,  
        title: 'NodeBlog',
        user: req.session.user  
    });  
});

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

##多核多线程使用(cluster使用)
从0.6 版本开始,Node.js 提供了一个核心模块:cluster。cluster的功能是生成与􏱝当前进程􏰫相同的􏴸进程,并且􏺲允许􏴬进程和􏴸进程之间共享端口。Node.js另的􏰝一个核心模块 child_process 也提供了􏰫􏲬的进程生成功能,但最大的区别在于cluster允许跨进程端口复用,给我们的网络服务器开发带来了很大的方便􏺳

为了在外部模块调用app.js􏱦首先需要􏺴􏱒禁止服务器自启􏱠动。􏲪修改app.js,在app.listen (3000); 前后加上􏺵􏺭判断语􏷵:

if(!module.parent){
  http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
  });
}

这个语􏷵的功能是􏺵􏺭􏱝前模块是不是由其他模块调用的,如果不是,说􏴹它是􏰡接􏱠直接启动的,此时􏱠启动调试服务器。如果是,则不自动􏱠动服务器。以后􏰡直接调用node app.js服务器会􏰡接运行,但在其他模块中调用require(‘./app’) 则不会自动启动,需要􏲳显式地调用listen() 函数。

接下来就让我们通过cluster 调用app.js􏺳。创建cluster.js􏱦内容如下所示:

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

// 获取cpu数量
var numCPUs = os.cpus().length;

var workers = {};

if(cluster.isMaster){
    // 主线程分支
    cluster.on('death',function (worker) {
        // 当一个工作进程结束时,重启工作进程
        delete workers[worker.pid];
        worker = cluster.fork();
        workers[worker.pid] = worker;
    });

    // 初始开启与cpu数量相同的工作进程
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        workers[worker.pid] = worker;
    }
} else {
    // 工作进程分支,启动服务器
    var app = require('./app');
    var http = require('http');
    http.createServer(app).listen(app.get('port'), function(){
        console.log("Express server listening on port " + app.get('port'));
     });
}

// 当主进程被终止,关闭所有工作进程
process.on('SIGTERM',function () {
    for(var pid in workers){
        process.kill(pid);
    }
    process.exit(0);
});

对应修改app.js

var app = module.exports = express();

if(!module.parent){
    http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
  });
}

cluster.js 的功能是创建与CPU 核心个数􏰫同的服务器进程,以􏵎􏳢􏲉分利用多核CPU 的 资源。主进程生成􏹁􏹂个工作进程,并􏶍听工作进程结􏱟事件,􏱝工作进程结􏱟时,重新􏱠启动一个工作进程。分支进程产生时会自顶􏲌下重新􏵱行􏱝前程序,并通过分支􏺵􏺭进入工作进程分支,在其中读取模块并􏱠动服务器。通过cluster􏱠启动的工作进程可以􏰡接实现端口复用,因此所有工作进程只需􏶍听同一端口。􏱝主进程终􏱒时,还要主动关闭所有工作进程。

##启动脚本
接下来,我们还需要一个􏱠脚􏲓本来简化维􏷁工作。如果你维􏷁过Linux 服务器,会对 /etc/init.d/ 下面的􏲓脚本有印􏵑象。例如使用/etc/init.d/nginx start 和/etc/init.d/ nginx stop 可以􏱠动和关闭Nginx 服务器。我们通过bash也来实现一个􏰘􏲬的功能, 创建microblog 并使用chmod +x microblog 􏵝􏼢其􏵱执行权限脚本内容为:

#! /bin/sh

NODE_ENV=production
DAEMON="node cluster.js"
NAME=HINOCLab
DESC=HINOCLab
PIDFILE="HINOCLab.pid"

case "$1" in
    start)
        echo "starting $DESC: "
            nohup $DAEMON > /dev/null &
        echo $! > $PIDFILE
        echo "$NAME."
            ;;
    stop)
        echo "stoping $DESC: "
            pid='cat $PIDFILE'
        kill $pid
            rm $PIDFILE
        echo "$NAME."
            ;;
esac
exit 0

注意不能有多余的空格

##共享80端口
􏽂􏽃虚拟主机就是让多个网站共享使用同一服务器,同一IP地址通过域􏰥的不同来􏳾分请求。主流的HTTP服务器都提供了􏽂􏽃主机支持,如Nginx、Apache、IIS等。以Nginx为例,介绍如何通过􏲋􏲌代理实现Node.js 􏽂􏽃主机。

在Nginx中设置􏲋􏲌代理和􏽂􏽃主机非常简􏰒单,下面是配置文件的一个示例:

server {
    listen 80;
    server_name mysite.com;
    location / {
    proxy_pass http://localhost:3000;
    }
}

这个配置文件的功能是􏶍听访问mysite.com 80 端口的请求,并将所有的请求􏴔发给 http://localhost:3000, 到的Node.js 服务器。现在访问http://mysite.com/,就􏰫􏱝于服务器访问 http://localhost:3000 了。
在􏳸加了􏽂􏽃主机以后,还可以在Nginx配置文件中􏳸加访问􏷦􏴠文件的规则(具体请
参考Nginx文档),􏷫去app.js中的app.use(express.static(__dirname + ‘/public’));。 这􏵌可以􏰡接让Nginx 来处理􏷦􏴠文件,􏰕少􏲋􏲌代理以及Node.js 的开销。