NodeJS 不死行程

Linux 系統中的服務,通常都是以 Daemon 的形式存在,Daemon 泛指一種在背景執行的程序,通常可以長時間工作不會中斷。最近剛好要將一個 Node.JS 所設計的系統執行在 Linux 中,由於 Linux 在慣例上會把服務封裝為 Linux Service 並且將啟動器集中在 /etc/init.d 目錄中進行管理。能夠遵循 Linux 的慣例佈署系統是個好方法,免得換了一個人維護就得換顆腦袋。
談到 Node.JS 就頭大了,Node.JS 是單執行緒,當程式拋出例外錯誤而又沒有順利捕捉處理,就會造成程式中斷,服務也就同時中斷。今天順便介紹 forever 這個套件,顧名思義能讓您的 NodeJS 程序永恆地執行下去,安裝方式很簡單,如下:
# npm install forever -g
假設您的應用程式為 app.js,那麼安裝好 forever 之後就可以透過以下命令啟動:
$ forever start app.js
這樣當 app.js 錯誤中斷時,就會自動重新啟動,當然這個方法不是正解,能夠搭配其他方法監控服務會比較保險,像是 Log 與 Notification Email 等等機制。
Linux Service
接下來我們需要撰寫一支 Shell Script 來封裝 forever + Node.JS,這支 Script 需放置於 /etc/init.d 目錄中,這樣一來我們就可以統一透過以下 service 命命管理服務,假設檔名為 my-service,服務執行如下:
# service my-service start
# service my-service stop
# service my-service restart
# service my-service status
Script 內容如下 (GitHub),其中停用 Service 的方法,如果改用 forever stop 取代 kill 會更好:
#!/bin/sh
### BEGIN INIT INFO
# Provides:
# Required-Start: my-service
# Required-Stop: my-service
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: My Linux Service
### END INIT INFO
set -e
SERVICE_NAME=`basename $0`
PIDFILE=/var/run/myserv.pid
LOGPATH="/var/log/${SERVICE_NAME}"
FOREVER_BIN=`which forever`
APP_PATH="/var/share/work-js/app.js"
case $1 in
start)
if [ -e "${PIDFILE}" ]; then
PID=`cat ${PIDFILE}`
echo "Service is running already. (PID=${PID})"
exit 1
fi
if [ ! -d "${LOGPATH}" ]; then
mkdir -p "${LOGPATH}"
fi
PID=`ps aux | grep ${APP_PATH} | head -n1 | awk '{print $2}'`
${FOREVER_BIN} -a -l "${LOGPATH}/service.log" -o "${LOGPATH}/out.log" -e "${LOGPATH}/error.log" start ${APP_PATH} > "${LOGPATH}/start.log"
rm -rf ${PIDFILE}
echo "${PID}" > ${PIDFILE}
echo "Service ${SERVICE_NAME} start. PID=${PID}"
;;
stop)
if [ ! -e "${PIDFILE}" ]; then
echo "Service is not running."
else
PID=`cat ${PIDFILE}`
kill ${PID} || true
rm -rf ${PIDFILE}
echo "Service ${SERVICE_NAME} stop. PID=${PID}"
fi
;;
restart)
$0 stop
sleep 1
$0 start
;;
status)
if [ -e "${PIDFILE}" ]; then
PID=`cat ${PIDFILE}`
echo "Service is running. (PID=${PID})"
else
echo "Service is not running."
fi
;;
*)
echo "Usage: ${0} {start|stop|restart|status}"
exit 1
;;
esac
