使用后台服务监护工具有很多好处:
- 程序崩溃时自动拉起
- 程序日志聚合(你的系统有多个模块或多个进程的时候很有必要)
- 代码更新时自动重启服务
node.js下最常用的后台服务监护工具有:forever 、pm2 。
forever 先出现,pm2 后出现功能更丰富,下面是特性对比:
Feature | Forever | PM2 |
---|---|---|
Keep Alive | ✔ | ✔ |
Coffeescript | ✔ | |
Log aggregation | ✔ | |
API | ✔ | |
Terminal monitoring | ✔ | |
Clustering | ✔ | |
JSON configuration | ✔ |
我在3个项目中使用 forever ,多次重启出错后,决定转向 pm2 ,目前我已经在两个较小的项目中成功使用 pm2 。
forever
安装
npm install -g forever
配置
启动脚本
start.sh
#!/bin/bash export PATH=$PATH:`pwd`/node/bin:`pwd`/../node/bin:`pwd`/node_modules/forever/bin:/usr/local/node/bin export NODE_ENV=${NODE_ENV:-production} export NODE_CONFIG_DIR=`pwd`/config SCRIPT=`pwd`/src/index.js LOGFILE=`pwd`/run.log running=`forever list | grep "$SCRIPT" | grep -v grep | wc -l` if [ $running -lt 1 ]; then forever start --spinSleepTime=10000 --killSignal=SIGINT --pidFile=`pwd`/run.pid -l $LOGFILE -a -w --watchDirectory=`pwd`/src --watchIgnore=".svn/*" "$SCRIPT" echo -e "\nRunning." else echo -e "\nAlready running." fi forever list | grep "$SCRIPT"
停止脚本
stop.sh
#!/bin/bash export PATH=$PATH:`pwd`/node/bin:`pwd`/../node/bin:`pwd`/node_modules/forever/bin:/usr/local/node/bin SCRIPT=`pwd`/src/index.js forever stop "$SCRIPT"
重启脚本
restart.sh
#!/bin/bash export PATH=$PATH:`pwd`/node/bin:`pwd`/../node/bin:`pwd`/node_modules/forever/bin:/usr/local/node/bin SCRIPT=`pwd`/src/index.js forever restart "$SCRIPT" || ./start.sh
用法
启动
./start.sh
停止
./stop.sh
重启
./restart.sh
- 缺点
程序退出过程中的日志无法捕获
参见:no logging after graceful shutdown #385
应该是forever通过信号通知程序退出后,不再捕获程序的日志输出,程序退出的这段时间内日志丢失。
一个补丁方案:程序收到forever的退出信号后将日志直接写到日志文件(正常情况下是由forever捕获程序的错误输出写日志文件)。
重启可能失败
代码更新后,forever会发信号重启进程,但是程序始终重启不成功,出现大量下面的日志:
Error: bind EADDRINUSE
怀疑跟node.js的cluster中master自动拉起slave的行为相冲突,此时只有一个forever实例在运行,这种情况占比很高。
另外crontab中调用start.sh也可能和forever相冲突,当node全退出时,可能启动多个forever实例,这种情况占比稍低。
另外一种情况是node.js出问题了CPU及内存100%占用,此时普通的kill杀不死(必须得kill -9),forever误认为已成功结束node.js进程,然后拉起新的进程。
未内置支持开机启动
可以直接放在crontab每分钟调用一次
start.sh
来实现,万一连forever进程都挂了,可以全部拉起来。开机启动不内置则意味着一百个人有一百种做法,带来不必要的争议。允许程序同时启动多个实例
forever未对启动的程序进行唯一性标识,导致程序可能意外启动多个实例,多个实例之间往往相冲突,降低了系统可用性。
而由程序自已来实现单实例运行是很困难的,forever会不断地拉起退出的多余副本。
未内置支持cluster以及优雅重启
部署代码重启程序过程中会停止服务几秒钟。
pm2
安装
npm install -g pm2
配置
以 upload-fiddle 项目为例。
统一配置其它脚本需要的环境变量
.bashrc
export PATH=`pwd`/node/bin:`pwd`/../node/bin:`pwd`/node_modules/pm2/bin:/usr/local/node/bin:$PATH export NODE_ENV=${NODE_ENV:-production} export NODE_CONFIG_DIR=`pwd`/config export APP_NAME="upload-fiddle" export APP_SCRIPT=`pwd`/src/index.js
启动脚本
start.sh
#!/bin/bash source .bashrc pm2 --node-args="--harmony" -n "$APP_NAME" start "$APP_SCRIPT" -i 0 --watch "`pwd`/src/*.js"
停止脚本
stop.sh
#!/bin/bash source .bashrc pm2 --node-args="--harmony" stop "$APP_NAME"
重启脚本
restart.sh
#!/bin/bash source .bashrc pm2 --node-args="--harmony" restart "$APP_NAME"
用法
启动
./start.sh
停止
./stop.sh
重启
./restart.sh
- 缺点
程序退出过程中的日志无法捕获?
不一定。使用
pm2 stop
会有同样的问题,但是pm2支持优雅退出(pm2 gracefulReload
),此时不但退出过程中的日志能够正常捕获,而且可以实现服务0停机时间。重启可能失败
是的。=pm2 restart= 并没有采用激进的措施(kill -9)确保旧进程结束。重现步骤:用gdb调试运行中的node进程(gdb node <PID>后不执行任何gdb命令),然后用pm2 restart重启服务,此时旧的进程杀不死,新的进程被创建。
允许程序同时启动多个实例
pm2对启动的程序进行了唯一性标识,但是它将启动的信息保存在了当前用户的home目录下(~/.pm2),所以使用其它帐号时还是有能够启动多个程序实例,对于这一点forever也存在同样的问题。
对于服务器来说,多帐号是常态,应该默认防止这种问题发生。